From 24f7521a2211a4a9b6b8680f9fbf320e7fe3618d Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 27 Sep 2023 22:07:19 +0200 Subject: [PATCH 01/19] Add total contract deposit --- crates/cargo-contract/src/cmd/mod.rs | 9 ++- crates/extrinsics/src/lib.rs | 54 ++++++++++++++++++ .../src/runtime_api/contracts_runtime.scale | Bin 29868 -> 37439 bytes 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index 6fcac4275..64e0f5256 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -119,7 +119,7 @@ impl CLIExtrinsicOpts { } } -const STORAGE_DEPOSIT_KEY: &str = "Storage Deposit"; +const STORAGE_DEPOSIT_KEY: &str = "Storage Total Deposit"; pub const MAX_KEY_COL_WIDTH: usize = STORAGE_DEPOSIT_KEY.len() + 1; /// Print to stdout the fields of the result of a `instantiate` or `call` dry-run via RPC. @@ -228,10 +228,15 @@ pub fn basic_display_format_contract_info(info: &ContractInfo) { MAX_KEY_COL_WIDTH ); name_value_println!( - "Storage Deposit", + "Storage Item Deposit", format!("{:?}", info.storage_item_deposit()), MAX_KEY_COL_WIDTH ); + name_value_println!( + "Storage Total Deposit", + format!("{:?}", info.storage_total_deposit()), + MAX_KEY_COL_WIDTH + ); } /// Display all contracts addresses in a formatted way diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index ca91641a8..2a63616a8 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -303,6 +303,44 @@ pub fn parse_code_hash(input: &str) -> Result<::Hash> { Ok(arr.into()) } +/// Calculate total contract deposit +async fn get_contract_total_deposit( + storage_base_deposit: Balance, + storage_item_deposit: Balance, + storage_byte_deposit: Balance, + client: &Client, +) -> Result { + let contract_pallet_version = fetch_contracts_pallet_version(client).await?; + let mut contract_deposit = storage_base_deposit + .saturating_add(storage_item_deposit) + .saturating_add(storage_byte_deposit); + + // From contracts pallet version 10 deposit calculation has changed + if contract_pallet_version >= 10 { + let existential_deposit_address = + api::constants().balances().existential_deposit(); + let existential_deposit = client.constants().at(&existential_deposit_address)?; + contract_deposit = contract_deposit.saturating_sub(existential_deposit); + } + Ok(contract_deposit) +} + +/// Fetch contracts pallet version from the storage using the provided client. +async fn fetch_contracts_pallet_version(client: &Client) -> Result { + let hash_pallet = hashing::twox_128(b"Contracts"); + let hash_version = hashing::twox_128(b":__STORAGE_VERSION__:"); + let key = [hash_pallet, hash_version].concat(); + + let version = client + .rpc() + .storage(key.as_slice(), None) + .await? + .ok_or(anyhow!("Failed to get storage version of contracts pallet"))? + .0; + let version = u16::decode(&mut version.as_slice())?; + Ok(version) +} + /// Fetch the contract info from the storage using the provided client. pub async fn fetch_contract_info( contract: &AccountId32, @@ -320,11 +358,21 @@ pub async fn fetch_contract_info( match contract_info_of { Some(info_result) => { let convert_trie_id = hex::encode(info_result.trie_id.0); + + let total_deposit = get_contract_total_deposit( + info_result.storage_base_deposit, + info_result.storage_item_deposit, + info_result.storage_byte_deposit, + client, + ) + .await?; + Ok(Some(ContractInfo { trie_id: convert_trie_id, code_hash: info_result.code_hash, storage_items: info_result.storage_items, storage_item_deposit: info_result.storage_item_deposit, + storage_total_deposit: total_deposit, })) } None => Ok(None), @@ -337,6 +385,7 @@ pub struct ContractInfo { code_hash: CodeHash, storage_items: u32, storage_item_deposit: Balance, + storage_total_deposit: Balance, } impl ContractInfo { @@ -364,6 +413,11 @@ impl ContractInfo { pub fn storage_item_deposit(&self) -> Balance { self.storage_item_deposit } + + /// Return the storage item deposit of the contract. + pub fn storage_total_deposit(&self) -> Balance { + self.storage_total_deposit + } } /// Fetch the contract wasm code from the storage using the provided client and code hash. diff --git a/crates/extrinsics/src/runtime_api/contracts_runtime.scale b/crates/extrinsics/src/runtime_api/contracts_runtime.scale index d61d093bafa0b24b78b62fd55c6d59b54c6fc17e..ff0c20300c4a07149667be6517645fdd725c7f29 100644 GIT binary patch delta 10957 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 UYaQwC(BfJ9Jaj%k+8ZMO3l6}2{r~^~ delta 3775 zcmbtX4{RId75|=dZQa&w+|@}gsgqn1w@p(gjZ-@^H0=Uu(srb6+Jv_0zstq`Vjr>3 z=I%@rC~8X^pwL3fV{Aobf{jT-V$+bJYBkD018q!W8yjdK6{&1YV$*<1Ak=Byw0D<; zoM(wKO|m|J_ul*7_kQnt-+S-->C>)f{{r*U+*C^6hcolOu`tNkoVl!#vAI6_( zw_x3B5zDLNa2S=U8aRr*4OJlG(b@($fuGdfNPGBdb`{av@k))*5CH!!9_z?OH!$$V z6;ml77)r_v;Jn%cMXMutsbRrN34&djINz)o$&P_9(Xc?;v|v^-o~aRS(<1nc1)U_7?^W(4Uq%VLq)pt&tHcS8Nz=VcPMXUceU>`Sh zf*;oe9x05%Q@Cw;4lg&|K)x?uO=pLrt1HW9!ao@2CfC!drW)z{@$BkBPXI*g6v&0_ z89L6TxFo2;IlL=aMOWjoU@e9E`Cun(nxsP+kSj;&9Yk^>}X6$rMNDWM!K=4gX34?ah)56gCr#sLr(FL0-m15@Xw6zC9jMT=+B4h`Wr&t?qO zl=&T8o3#XcLRAzkN~o@?Pog0HvLRyvrirZUQBxv*B+;NVJ>TQy^( z;4HjWU^rw5af&C%FS5-*d2M~v!S1uDwnJknO1bg>JcgjE8Ky!B)bcD|T)T<@{AulS z8s~>8Z(vJUq;XTY&hJp#VI>H8l*eMqsN5E5_v49hL$Sbciomr^3H#)YEt_VmbRl#5$eJks2ER5!nDg#v9vf;CUQu zUj-NP$J>Qccok1}H`92byB&UuS84nOHuluPukeZ$x1{0;Bh(*vL3Z}v>o z9);GMr!LR-5j!(1a#0iY>1Kv4!!;vU#q}pe2&Bz(uT0+(MmN;XxnU>9E zR&ksXaI^m^E>J1tO6n^}&SAdo=G-LL1s1^Q-WFj#uqfU=xC|%udSMYhws$wB^pE#e zRD1^FHv1}5-KRsQ5V2B;gw^{btElg*fF-zV-_EAP-YyCfm<;r*2~D9p$>AMe_ZS5q zP%)-$s1)BsE^t-i@Aox_${=7dDR9&nR04B@nB+>F>tz7qW_v~(@);4M8bkX-Ee_YP zEiPwyT@FR{bvaJ%ud3-2x}g``?(L%j>mwVOr&}k~y>{^(#2?A^`M>Tr03vwsU_IP` z#}2N%B?Po+J!!O8g{Yj*EL%@=`lYS2bPolXY9Zel4ZsAV>`1-5~1D~DA!=!_OXlAl%%=kWb@Ui930J~Ix?zv(UpTl=~)b#{lX|iG| zEfAsADwR~Dja-_?{kVRlf)dA$WEK1nvm;IQjwl6@sw^GI69B?a+S|6ncmZD=*}O;s zw|Hwdl~Ll>C0vrIAQY<-yWuPzORSS#aRPqD3D}On*YOGuxnBFfjC!resA_yW*;y(1 zr!yCL>0Zt@9ul^wEz%oK!`>(|?0qt<1|L@yziJ7-J#&jxhveIU6}Pn%_FOOHiQ86y zfZs7zx}-<(=SGi9`ZkuD8^Me(@=2m8AYy zlE-r?{$f6I*XIHZilv`v2&j z2ZiMzoWfU*m*XSHSHmiN>G&U^8J{}w%aTgC3AG7?GIZU$u4D&PW9PlMEvTD%1nhuY z@%ei{@(qI99IJ%JsgBHU$a(&S`#N2={%a@4N>m8sjR&g16&TMy@?ZnFE5VcEn&%i^ zI#sz~`r|Y(gopeTKW}ncVE1WbdVU(;Jl#v&BM+^>jSuBv58mETUU~)^ry4i= zS#lApAAVqpQ-21$R?m8A`o-e4pDC|{Co654?j!KI+*17X;cn``N!5}wP|$O~2{<%) TtR(O*3D?*aQaO!i0_ From a94edf03855021ccf4d73f4cd535ee20b161a3d9 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 28 Sep 2023 10:24:43 +0200 Subject: [PATCH 02/19] Typo fixed, changelog updated --- CHANGELOG.md | 1 + crates/extrinsics/src/lib.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c868d15..9e2eef6cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `--binary` flag for `info` command - [#1311](https://github.com/paritytech/cargo-contract/pull/1311/) - Add `--all` flag for `info` command - [#1319](https://github.com/paritytech/cargo-contract/pull/1319) - Fix for a Url to String conversion in `info` command - [#1330](https://github.com/paritytech/cargo-contract/pull/1330) +- Add `Storage Total Deposit` to `info` command output - [#1347](https://github.com/paritytech/cargo-contract/pull/1347) ## [4.0.0-alpha] diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 2a63616a8..8b32df66b 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -303,7 +303,7 @@ pub fn parse_code_hash(input: &str) -> Result<::Hash> { Ok(arr.into()) } -/// Calculate total contract deposit +/// Calculate the total contract deposit. async fn get_contract_total_deposit( storage_base_deposit: Balance, storage_item_deposit: Balance, @@ -315,7 +315,7 @@ async fn get_contract_total_deposit( .saturating_add(storage_item_deposit) .saturating_add(storage_byte_deposit); - // From contracts pallet version 10 deposit calculation has changed + // From contracts pallet version 10 deposit calculation has changed. if contract_pallet_version >= 10 { let existential_deposit_address = api::constants().balances().existential_deposit(); @@ -325,7 +325,7 @@ async fn get_contract_total_deposit( Ok(contract_deposit) } -/// Fetch contracts pallet version from the storage using the provided client. +/// Fetch the contracts pallet version from the storage using the provided client. async fn fetch_contracts_pallet_version(client: &Client) -> Result { let hash_pallet = hashing::twox_128(b"Contracts"); let hash_version = hashing::twox_128(b":__STORAGE_VERSION__:"); @@ -445,7 +445,7 @@ fn parse_contract_account_address( storage_contract_root_key_len: usize, ) -> Result { // storage_contract_account_key is a concatenation of contract_info_of root key and - // Twox64Concat(AccountId) + // Twox64Concat(AccountId). let mut account = storage_contract_account_key .get(storage_contract_root_key_len + 8..) .ok_or(anyhow!("Unexpected storage key size"))?; @@ -454,7 +454,7 @@ fn parse_contract_account_address( } /// Fetch all contract addresses from the storage using the provided client and count of -/// requested elements starting from an optional address +/// requested elements starting from an optional address. pub async fn fetch_all_contracts( client: &Client, count: u32, From 64f2fe0f94ca42de3912fa139e204e857ed67f00 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 28 Sep 2023 13:16:16 +0200 Subject: [PATCH 03/19] Rename item deposit to items deposit --- crates/cargo-contract/src/cmd/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index 64e0f5256..0f844bf4f 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -228,7 +228,7 @@ pub fn basic_display_format_contract_info(info: &ContractInfo) { MAX_KEY_COL_WIDTH ); name_value_println!( - "Storage Item Deposit", + "Storage Items Deposit", format!("{:?}", info.storage_item_deposit()), MAX_KEY_COL_WIDTH ); From 1234e2149abbe2014d4d948864821ee08b057161 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 20 Oct 2023 15:53:50 +0200 Subject: [PATCH 04/19] Use of dynamic values --- crates/extrinsics/src/lib.rs | 82 +++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 8b32df66b..c8e7465f1 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -28,7 +28,13 @@ mod upload; #[cfg(feature = "integration-tests")] mod integration_tests; -use subxt::utils::AccountId32; +use subxt::{utils::AccountId32, + ext::scale_value::{ + Composite, + Value, + ValueDef, + }, +}; use anyhow::{ anyhow, @@ -116,6 +122,9 @@ pub type Client = OnlineClient; pub type Balance = u128; pub type CodeHash = ::Hash; +use api::runtime_types::pallet_contracts::storage::ContractInfo; +type StorageVersion = u16; + /// Contract artifacts for use with extrinsic commands. #[derive(Debug)] pub struct ContractArtifacts { @@ -325,8 +334,42 @@ async fn get_contract_total_deposit( Ok(contract_deposit) } +async fn get_contract_total_deposit2(deposit_account: &AccountId32, pallet_contracts_version: StorageVersion) -> Result { + Ok(1) +} + +/// Try to extract the given field from a dynamic [`Value`]. +/// +/// Returns `Err` if: +/// - The value is not a [`Value::Composite`] with [`Composite::Named`] fields +/// - The value does not contain a field with the given name. +fn get_composite_field_value<'a, T, C>( + value: &'a Value, + field_name: &str, +) -> Result<&'a Value> +where + C: subxt::Config, +{ + if let ValueDef::Composite(Composite::Named(fields)) = &value.value { + let (_, field) = fields + .iter() + .find(|(name, _)| name == field_name) + .ok_or_else(|| { + anyhow!("No field named '{}' found", field_name) + })?; + Ok(field) + } else { + anyhow::bail!("Expected a composite type with named fields") + } +} + +fn getDepositAccount(contract_info: &ContractInfo) -> Option { + //let deposit_account = get_composite_field_value::<_, DefaultConfig>(contract_info, "deposit_account")?; + None +} + /// Fetch the contracts pallet version from the storage using the provided client. -async fn fetch_contracts_pallet_version(client: &Client) -> Result { +async fn fetch_contracts_pallet_version(client: &Client) -> Result { let hash_pallet = hashing::twox_128(b"Contracts"); let hash_version = hashing::twox_128(b":__STORAGE_VERSION__:"); let key = [hash_pallet, hash_version].concat(); @@ -337,10 +380,30 @@ async fn fetch_contracts_pallet_version(client: &Client) -> Result { .await? .ok_or(anyhow!("Failed to get storage version of contracts pallet"))? .0; - let version = u16::decode(&mut version.as_slice())?; + let version = StorageVersion::decode(&mut version.as_slice())?; + println!("pallet version {}", version); Ok(version) } +async fn get_account_balance(account: &AccountId32, client: &Client) -> Result<(Balance, Balance)> { + let storage_query = api::storage().system().account(account); + let result = client + .storage() + .at_latest() + .await? + .fetch(&storage_query) + .await?; + let result: api::runtime_types::frame_system::AccountInfo> = result.unwrap(); + println!( + "contract balance: free: {} reserved: {}", + result.data.free, result.data.reserved + ); + let ex = api::constants().balances().existential_deposit(); + let value = client.constants().at(&ex)?; + println!("existential deposit: {}", value); + + Ok((result.data.free, result.data.reserved)) +} /// Fetch the contract info from the storage using the provided client. pub async fn fetch_contract_info( contract: &AccountId32, @@ -348,17 +411,22 @@ pub async fn fetch_contract_info( ) -> Result> { let info_contract_call = api::storage().contracts().contract_info_of(contract); - let contract_info_of = client + let contract_info_of: Option = client .storage() .at_latest() .await? .fetch(&info_contract_call) .await?; - + println!("Contract account balance"); + get_account_balance(contract, client).await?; match contract_info_of { Some(info_result) => { let convert_trie_id = hex::encode(info_result.trie_id.0); + + println!("Deposit account balance"); + get_account_balance(&info_result.deposit_account.0, client).await?; + println!("base: {}, item: {}, byte: {}", info_result.storage_base_deposit, info_result.storage_item_deposit, info_result.storage_byte_deposit); let total_deposit = get_contract_total_deposit( info_result.storage_base_deposit, info_result.storage_item_deposit, @@ -367,6 +435,10 @@ pub async fn fetch_contract_info( ) .await?; + + + get_contract_total_deposit2(contract, ).await?; + Ok(Some(ContractInfo { trie_id: convert_trie_id, code_hash: info_result.code_hash, From fee63d0e3c7e19514d4b76e6e85cc1a185cdd4f9 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 7 Nov 2023 14:57:52 +0100 Subject: [PATCH 05/19] Added dynamic types --- Cargo.lock | 1 + crates/cargo-contract/src/cmd/info.rs | 7 +- crates/extrinsics/Cargo.toml | 1 + crates/extrinsics/src/lib.rs | 205 +++++++++----------------- 4 files changed, 74 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9167dcef..40d696d3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,6 +1035,7 @@ dependencies = [ "predicates", "regex", "rust_decimal", + "scale-decode", "scale-info", "serde", "serde_json", diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 40623fa51..508885bdc 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -101,12 +101,7 @@ impl InfoCommand { .as_ref() .expect("Contract argument was not provided"); - let info_to_json = fetch_contract_info(contract, &rpc, &client) - .await? - .ok_or(anyhow!( - "No contract information was found for account id {}", - contract - ))?; + let info_to_json = fetch_contract_info(contract, &rpc, &client).await?; // Binary flag applied if self.binary { diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index 60f961102..e89a4ab3b 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -35,6 +35,7 @@ sp-runtime = "25.0.0" sp-weights = "21.0.0" pallet-contracts-primitives = "25.0.0" scale-info = "2.8.0" +scale-decode = "0.9.0" subxt = "0.32.1" subxt-signer = { version = "0.32.1", features = ["subxt", "sr25519"] } hex = "0.4.3" diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 1c7169851..bd5454ca4 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -28,18 +28,13 @@ mod upload; #[cfg(feature = "integration-tests")] mod integration_tests; -<<<<<<< HEAD -use subxt::{utils::AccountId32, - ext::scale_value::{ - Composite, - Value, - ValueDef, - }, +use subxt::{ + ext::scale_value::Value, + storage::dynamic, + utils::AccountId32, }; -======= + use colored::Colorize; -use subxt::utils::AccountId32; ->>>>>>> origin/master use anyhow::{ anyhow, @@ -118,9 +113,6 @@ pub type Client = OnlineClient; pub type Balance = u128; pub type CodeHash = ::Hash; -use api::runtime_types::pallet_contracts::storage::ContractInfo; -type StorageVersion = u16; - /// Contract artifacts for use with extrinsic commands. #[derive(Debug)] pub struct ContractArtifacts { @@ -344,97 +336,36 @@ pub fn parse_code_hash(input: &str) -> Result<::Hash> { Ok(arr.into()) } -/// Calculate the total contract deposit. -async fn get_contract_total_deposit( - storage_base_deposit: Balance, - storage_item_deposit: Balance, - storage_byte_deposit: Balance, - client: &Client, -) -> Result { - let contract_pallet_version = fetch_contracts_pallet_version(client).await?; - let mut contract_deposit = storage_base_deposit - .saturating_add(storage_item_deposit) - .saturating_add(storage_byte_deposit); - - // From contracts pallet version 10 deposit calculation has changed. - if contract_pallet_version >= 10 { - let existential_deposit_address = - api::constants().balances().existential_deposit(); - let existential_deposit = client.constants().at(&existential_deposit_address)?; - contract_deposit = contract_deposit.saturating_sub(existential_deposit); - } - Ok(contract_deposit) -} - -async fn get_contract_total_deposit2(deposit_account: &AccountId32, pallet_contracts_version: StorageVersion) -> Result { - Ok(1) -} - -/// Try to extract the given field from a dynamic [`Value`]. -/// -/// Returns `Err` if: -/// - The value is not a [`Value::Composite`] with [`Composite::Named`] fields -/// - The value does not contain a field with the given name. -fn get_composite_field_value<'a, T, C>( - value: &'a Value, - field_name: &str, -) -> Result<&'a Value> -where - C: subxt::Config, -{ - if let ValueDef::Composite(Composite::Named(fields)) = &value.value { - let (_, field) = fields - .iter() - .find(|(name, _)| name == field_name) - .ok_or_else(|| { - anyhow!("No field named '{}' found", field_name) - })?; - Ok(field) - } else { - anyhow::bail!("Expected a composite type with named fields") - } -} - -fn getDepositAccount(contract_info: &ContractInfo) -> Option { - //let deposit_account = get_composite_field_value::<_, DefaultConfig>(contract_info, "deposit_account")?; - None +#[derive(scale_decode::DecodeAsType, Debug)] +struct AccountData { + pub free: Balance, + pub reserved: Balance, } -/// Fetch the contracts pallet version from the storage using the provided client. -async fn fetch_contracts_pallet_version(client: &Client) -> Result { - let hash_pallet = hashing::twox_128(b"Contracts"); - let hash_version = hashing::twox_128(b":__STORAGE_VERSION__:"); - let key = [hash_pallet, hash_version].concat(); - - let version = client - .rpc() - .storage(key.as_slice(), None) - .await? - .ok_or(anyhow!("Failed to get storage version of contracts pallet"))? - .0; - let version = StorageVersion::decode(&mut version.as_slice())?; - println!("pallet version {}", version); - Ok(version) -} +/// Return the account data for an account ID. +async fn get_account_balance( + account: &AccountId32, + rpc: &LegacyRpcMethods, + client: &Client, +) -> Result { + let storage_query = + subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]); + let best_block = get_best_block(rpc).await?; -async fn get_account_balance(account: &AccountId32, client: &Client) -> Result<(Balance, Balance)> { - let storage_query = api::storage().system().account(account); - let result = client + let account = client .storage() - .at_latest() - .await? + .at(best_block) .fetch(&storage_query) - .await?; - let result: api::runtime_types::frame_system::AccountInfo> = result.unwrap(); - println!( - "contract balance: free: {} reserved: {}", - result.data.free, result.data.reserved - ); - let ex = api::constants().balances().existential_deposit(); - let value = client.constants().at(&ex)?; - println!("existential deposit: {}", value); + .await? + .ok_or_else(|| anyhow::anyhow!("Failed to fetch account data"))?; - Ok((result.data.free, result.data.reserved)) + #[derive(scale_decode::DecodeAsType, Debug)] + struct AccountInfo { + data: AccountData, + } + + let data = account.as_type::()?.data; + Ok(data) } /// Fetch the hash of the *best* block (included but not guaranteed to be finalized). @@ -451,48 +382,54 @@ pub async fn fetch_contract_info( contract: &AccountId32, rpc: &LegacyRpcMethods, client: &Client, -) -> Result> { - let info_contract_call = api::storage().contracts().contract_info_of(contract); - +) -> Result { let best_block = get_best_block(rpc).await?; - let contract_info_of = client + let contract_info_address = dynamic( + "Contracts", + "ContractInfoOf", + vec![Value::from_bytes(contract)], + ); + let contract_info = client .storage() .at(best_block) - .fetch(&info_contract_call) - .await?; - println!("Contract account balance"); - get_account_balance(contract, client).await?; - match contract_info_of { - Some(info_result) => { - let convert_trie_id = hex::encode(info_result.trie_id.0); - - - println!("Deposit account balance"); - get_account_balance(&info_result.deposit_account.0, client).await?; - println!("base: {}, item: {}, byte: {}", info_result.storage_base_deposit, info_result.storage_item_deposit, info_result.storage_byte_deposit); - let total_deposit = get_contract_total_deposit( - info_result.storage_base_deposit, - info_result.storage_item_deposit, - info_result.storage_byte_deposit, - client, - ) - .await?; - - - - get_contract_total_deposit2(contract, ).await?; + .fetch(&contract_info_address) + .await? + .ok_or_else(|| anyhow::anyhow!("Failed to fetch contract info"))?; + + #[derive(scale_decode::DecodeAsType, Debug)] + pub struct BoundedVec(pub ::std::vec::Vec); + #[derive(scale_decode::DecodeAsType, Debug)] + struct ContractInfoOf { + trie_id: BoundedVec, + code_hash: CodeHash, + storage_items: u32, + storage_item_deposit: Balance, + } + #[derive(scale_decode::DecodeAsType)] + struct DepositAccount { + deposit_account: AccountId32, + } - Ok(Some(ContractInfo { - trie_id: convert_trie_id, - code_hash: info_result.code_hash, - storage_items: info_result.storage_items, - storage_item_deposit: info_result.storage_item_deposit, - storage_total_deposit: total_deposit, - })) + let total_balance: Balance = match contract_info.as_type::() { + Ok(account) => { + // StorageVersion >= 10 and < 15 contains deposit in separate account + let deposit_account = AccountId32(account.deposit_account.0); + get_account_balance(&deposit_account, rpc, client) + .await? + .free } - None => Ok(None), - } + Err(_) => get_account_balance(contract, rpc, client).await?.reserved, + }; + + let info = contract_info.as_type::()?; + Ok(ContractInfo { + trie_id: hex::encode(info.trie_id.0), + code_hash: info.code_hash, + storage_items: info.storage_items, + storage_item_deposit: info.storage_item_deposit, + storage_total_deposit: total_balance, + }) } #[derive(serde::Serialize)] From 032c5d0cee0625d2519dc3ef279d786028fa00fe Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 7 Nov 2023 15:23:19 +0100 Subject: [PATCH 06/19] Cleanup --- crates/cargo-contract/src/cmd/mod.rs | 8 ++++---- crates/extrinsics/src/lib.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index 1ffa7ed77..eb5747774 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -235,13 +235,13 @@ pub fn basic_display_format_extended_contract_info(info: &ExtendedContractInfo) MAX_KEY_COL_WIDTH ); name_value_println!( - "Source Language", - format!("{}", info.source_language), + "Storage Total Deposit", + format!("{:?}", info.storage_total_deposit), MAX_KEY_COL_WIDTH ); name_value_println!( - "Storage Total Deposit", - format!("{:?}", info.storage_total_deposit), + "Source Language", + format!("{}", info.source_language), MAX_KEY_COL_WIDTH ); } diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 71128b5bf..50b1ac40c 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -397,8 +397,12 @@ pub async fn fetch_contract_info( .at(best_block) .fetch(&contract_info_address) .await? - .ok_or_else(|| anyhow::anyhow!("Failed to fetch contract info"))?; - + .ok_or_else(|| { + anyhow!( + "No contract information was found for account id {}", + contract + ) + })?; #[derive(scale_decode::DecodeAsType, Debug)] pub struct BoundedVec(pub ::std::vec::Vec); #[derive(scale_decode::DecodeAsType, Debug)] From 7d650b566dc9001f09f9f7974d07615b6b67e1d0 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 7 Nov 2023 20:53:05 +0100 Subject: [PATCH 07/19] Fixed items deposit name --- crates/cargo-contract/src/cmd/info.rs | 4 ++-- crates/cargo-contract/src/cmd/mod.rs | 2 +- crates/extrinsics/src/lib.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index b12c07daf..492b621a3 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -149,7 +149,7 @@ pub struct ExtendedContractInfo { pub trie_id: String, pub code_hash: CodeHash, pub storage_items: u32, - pub storage_item_deposit: Balance, + pub storage_items_deposit: Balance, pub storage_total_deposit: Balance, pub source_language: String, } @@ -164,7 +164,7 @@ impl ExtendedContractInfo { trie_id: contract_info.trie_id().to_string(), code_hash: *contract_info.code_hash(), storage_items: contract_info.storage_items(), - storage_item_deposit: contract_info.storage_item_deposit(), + storage_items_deposit: contract_info.storage_items_deposit(), storage_total_deposit: contract_info.storage_total_deposit(), source_language: language, } diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index eb5747774..dd2a69b2b 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -231,7 +231,7 @@ pub fn basic_display_format_extended_contract_info(info: &ExtendedContractInfo) ); name_value_println!( "Storage Items Deposit", - format!("{:?}", info.storage_item_deposit), + format!("{:?}", info.storage_items_deposit), MAX_KEY_COL_WIDTH ); name_value_println!( diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 50b1ac40c..e1475986a 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -433,7 +433,7 @@ pub async fn fetch_contract_info( trie_id: hex::encode(info.trie_id.0), code_hash: info.code_hash, storage_items: info.storage_items, - storage_item_deposit: info.storage_item_deposit, + storage_items_deposit: info.storage_item_deposit, storage_total_deposit: total_balance, }) } @@ -453,7 +453,7 @@ pub struct ContractInfo { trie_id: String, code_hash: CodeHash, storage_items: u32, - storage_item_deposit: Balance, + storage_items_deposit: Balance, storage_total_deposit: Balance, } @@ -479,8 +479,8 @@ impl ContractInfo { } /// Return the storage item deposit of the contract. - pub fn storage_item_deposit(&self) -> Balance { - self.storage_item_deposit + pub fn storage_items_deposit(&self) -> Balance { + self.storage_items_deposit } /// Return the storage item deposit of the contract. From 63095235daf439fe82394c4d87df809834eac6f5 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 8 Nov 2023 12:49:35 +0100 Subject: [PATCH 08/19] Removed scale_decode dep and code cleanup --- Cargo.lock | 1 - crates/cargo-contract/src/cmd/mod.rs | 2 +- crates/extrinsics/Cargo.toml | 1 - crates/extrinsics/src/lib.rs | 28 +++++++++++++++++++--------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index befd00313..d3c70988c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1047,7 +1047,6 @@ dependencies = [ "predicates", "regex", "rust_decimal", - "scale-decode", "scale-info", "serde", "serde_json", diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index dd2a69b2b..4b7f2d738 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -235,7 +235,7 @@ pub fn basic_display_format_extended_contract_info(info: &ExtendedContractInfo) MAX_KEY_COL_WIDTH ); name_value_println!( - "Storage Total Deposit", + STORAGE_DEPOSIT_KEY, format!("{:?}", info.storage_total_deposit), MAX_KEY_COL_WIDTH ); diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index 3193b73d1..6f289eff1 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -34,7 +34,6 @@ sp-core = "22.0.0" sp-runtime = "25.0.0" sp-weights = "21.0.0" pallet-contracts-primitives = "25.0.0" -scale-decode = "0.9.0" scale-info = "2.10.0" subxt = "0.32.1" subxt-signer = { version = "0.32.1", features = ["subxt", "sr25519"] } diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index e1475986a..85ea2e548 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -29,12 +29,6 @@ mod upload; #[cfg(feature = "integration-tests")] mod integration_tests; -use subxt::{ - ext::scale_value::Value, - storage::dynamic, - utils::AccountId32, -}; - use colored::Colorize; use env_check::compare_node_env_with_contract; @@ -57,10 +51,17 @@ use scale::{ use subxt::{ blocks, config, + ext::{ + scale_decode, + scale_value::Value, + }, + storage::dynamic, tx, + utils::AccountId32, Config, OnlineClient, }; + use subxt_signer::sr25519::Keypair; use std::{ @@ -339,6 +340,7 @@ pub fn parse_code_hash(input: &str) -> Result<::Hash> { } #[derive(scale_decode::DecodeAsType, Debug)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct AccountData { pub free: Balance, pub reserved: Balance, @@ -362,6 +364,7 @@ async fn get_account_balance( .ok_or_else(|| anyhow::anyhow!("Failed to fetch account data"))?; #[derive(scale_decode::DecodeAsType, Debug)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct AccountInfo { data: AccountData, } @@ -404,8 +407,10 @@ pub async fn fetch_contract_info( ) })?; #[derive(scale_decode::DecodeAsType, Debug)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] pub struct BoundedVec(pub ::std::vec::Vec); #[derive(scale_decode::DecodeAsType, Debug)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct ContractInfoOf { trie_id: BoundedVec, code_hash: CodeHash, @@ -413,15 +418,20 @@ pub async fn fetch_contract_info( storage_item_deposit: Balance, } #[derive(scale_decode::DecodeAsType)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct DepositAccount { deposit_account: AccountId32, } let total_balance: Balance = match contract_info.as_type::() { Ok(account) => { - // StorageVersion >= 10 and < 15 contains deposit in separate account - let deposit_account = AccountId32(account.deposit_account.0); - get_account_balance(&deposit_account, rpc, client) + // Pallet-contracts [>=10, <15] store the contract's deposit as a free balance + // in a secondary account (deposit account). Other versions store it as + // reserved balance on the main contract's account. If the + // `deposit_account` field is present in a contract info structure, + // the contract's deposit is in this account. + let deposit_account = &account.deposit_account; + get_account_balance(deposit_account, rpc, client) .await? .free } From 5adbe28495c339e1e61283e554194c964d6e98c5 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 9 Nov 2023 12:51:00 +0100 Subject: [PATCH 09/19] Added unit test --- crates/extrinsics/src/lib.rs | 129 +++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 19 deletions(-) diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 85ea2e548..252b28486 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -51,8 +51,9 @@ use scale::{ use subxt::{ blocks, config, + dynamic::DecodedValueThunk, ext::{ - scale_decode, + scale_decode::DecodeAsType, scale_value::Value, }, storage::dynamic, @@ -339,7 +340,7 @@ pub fn parse_code_hash(input: &str) -> Result<::Hash> { Ok(arr.into()) } -#[derive(scale_decode::DecodeAsType, Debug)] +#[derive(DecodeAsType, Debug)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct AccountData { pub free: Balance, @@ -363,7 +364,7 @@ async fn get_account_balance( .await? .ok_or_else(|| anyhow::anyhow!("Failed to fetch account data"))?; - #[derive(scale_decode::DecodeAsType, Debug)] + #[derive(DecodeAsType, Debug)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct AccountInfo { data: AccountData, @@ -382,6 +383,18 @@ async fn get_best_block( .ok_or(subxt::Error::Other("Best block not found".into())) } +/// Decode the account id from the contract info +fn get_deposit_account_id(contract_info: &DecodedValueThunk) -> Result { + #[derive(DecodeAsType)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] + struct DepositAccount { + deposit_account: AccountId32, + } + + let account = contract_info.as_type::()?; + Ok(account.deposit_account) +} + /// Fetch the contract info from the storage using the provided client. pub async fn fetch_contract_info( contract: &AccountId32, @@ -406,10 +419,10 @@ pub async fn fetch_contract_info( contract ) })?; - #[derive(scale_decode::DecodeAsType, Debug)] + #[derive(DecodeAsType, Debug)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] pub struct BoundedVec(pub ::std::vec::Vec); - #[derive(scale_decode::DecodeAsType, Debug)] + #[derive(DecodeAsType, Debug)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] struct ContractInfoOf { trie_id: BoundedVec, @@ -417,21 +430,15 @@ pub async fn fetch_contract_info( storage_items: u32, storage_item_deposit: Balance, } - #[derive(scale_decode::DecodeAsType)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - struct DepositAccount { - deposit_account: AccountId32, - } - let total_balance: Balance = match contract_info.as_type::() { - Ok(account) => { - // Pallet-contracts [>=10, <15] store the contract's deposit as a free balance - // in a secondary account (deposit account). Other versions store it as - // reserved balance on the main contract's account. If the - // `deposit_account` field is present in a contract info structure, - // the contract's deposit is in this account. - let deposit_account = &account.deposit_account; - get_account_balance(deposit_account, rpc, client) + // Pallet-contracts [>=10, <15] store the contract's deposit as a free balance + // in a secondary account (deposit account). Other versions store it as + // reserved balance on the main contract's account. If the + // `deposit_account` field is present in a contract info structure, + // the contract's deposit is in this account. + let total_balance: Balance = match get_deposit_account_id(&contract_info) { + Ok(deposit_account) => { + get_account_balance(&deposit_account, rpc, client) .await? .free } @@ -607,6 +614,11 @@ impl From<&pallet_contracts_primitives::StorageDeposit> for StorageDepo #[cfg(test)] mod tests { + use std::collections::BTreeMap; + + use scale_info::form::PortableForm; + use subxt::ext::frame_metadata; + use super::*; #[test] @@ -645,4 +657,83 @@ mod tests { let url = url::Url::parse("wss://test.io/test/1").unwrap(); assert_eq!(url_to_string(&url), "wss://test.io:443/test/1"); } + + #[test] + fn deposit_decode_works() { + use scale_info::TypeInfo; + use subxt::metadata::DecodeWithMetadata; + + // subxt::utils::AccountId32 does not implement scale_info::TypeInfo + #[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] + struct AccountId32([u8; 32]); + + #[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)] + #[decode_as_type(crate_path = "subxt::ext::scale_decode")] + struct ContractInfoOf { + some_account: AccountId32, + deposit_account: AccountId32, + another_deposit_account: AccountId32, + } + + let contract_info_ty = scale_info::MetaType::new::(); + let unit = scale_info::MetaType::new::<()>(); + let mut types = scale_info::Registry::new(); + let contract_info_ty_id = types.register_type(&contract_info_ty); + let unit_id = types.register_type(&unit); + let types: scale_info::PortableRegistry = types.into(); + let contract_info = ContractInfoOf { + some_account: AccountId32([0u8; 32]), + deposit_account: AccountId32([1u8; 32]), + another_deposit_account: AccountId32([2u8; 32]), + }; + let bytes = contract_info.encode(); + + let contract_info_value_metadata: frame_metadata::v15::CustomValueMetadata< + PortableForm, + > = frame_metadata::v15::CustomValueMetadata { + ty: contract_info_ty_id, + value: contract_info.encode(), + }; + + let frame_metadata = frame_metadata::v15::RuntimeMetadataV15 { + types, + pallets: vec![], + extrinsic: frame_metadata::v15::ExtrinsicMetadata { + version: 0, + address_ty: unit_id, + call_ty: unit_id, + signature_ty: unit_id, + extra_ty: unit_id, + signed_extensions: vec![], + }, + ty: unit_id, + apis: vec![], + outer_enums: frame_metadata::v15::OuterEnums { + call_enum_ty: unit_id, + event_enum_ty: unit_id, + error_enum_ty: unit_id, + }, + custom: frame_metadata::v15::CustomMetadata { + map: BTreeMap::from_iter([( + "ContractInfoOf".to_string(), + contract_info_value_metadata, + )]), + }, + }; + + let metadata: subxt::metadata::types::Metadata = frame_metadata + .try_into() + .expect("metadata conversion must work"); + let contract_info_thunk = DecodedValueThunk::decode_with_metadata( + &mut &*bytes, + contract_info_ty_id.id, + &metadata.into(), + ) + .expect("contract info must be decoded"); + let deposit = get_deposit_account_id(&contract_info_thunk) + .expect("deposit account must be decoded from contract info"); + + assert_eq!(deposit.0, contract_info.deposit_account.0); + } } From ff23ea28aecd8c8dd5bb1e726ed9fa8e251af2af Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 10 Nov 2023 09:49:46 +0100 Subject: [PATCH 10/19] Updated unit test --- crates/extrinsics/src/lib.rs | 120 ++++++++---------- .../src/runtime_api/metadata_v11.scale | Bin 0 -> 29868 bytes 2 files changed, 51 insertions(+), 69 deletions(-) create mode 100644 crates/extrinsics/src/runtime_api/metadata_v11.scale diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 252b28486..60cd8ac8a 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -614,11 +614,6 @@ impl From<&pallet_contracts_primitives::StorageDeposit> for StorageDepo #[cfg(test)] mod tests { - use std::collections::BTreeMap; - - use scale_info::form::PortableForm; - use subxt::ext::frame_metadata; - use super::*; #[test] @@ -660,80 +655,67 @@ mod tests { #[test] fn deposit_decode_works() { - use scale_info::TypeInfo; - use subxt::metadata::DecodeWithMetadata; - - // subxt::utils::AccountId32 does not implement scale_info::TypeInfo - #[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - struct AccountId32([u8; 32]); - - #[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - struct ContractInfoOf { - some_account: AccountId32, - deposit_account: AccountId32, - another_deposit_account: AccountId32, - } - - let contract_info_ty = scale_info::MetaType::new::(); - let unit = scale_info::MetaType::new::<()>(); - let mut types = scale_info::Registry::new(); - let contract_info_ty_id = types.register_type(&contract_info_ty); - let unit_id = types.register_type(&unit); - let types: scale_info::PortableRegistry = types.into(); - let contract_info = ContractInfoOf { - some_account: AccountId32([0u8; 32]), - deposit_account: AccountId32([1u8; 32]), - another_deposit_account: AccountId32([2u8; 32]), + #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata_V11.scale")] + pub mod api_v11 {} + + use api_v11::runtime_types::{ + bounded_collections::bounded_vec::BoundedVec, + pallet_contracts::storage::{ + ContractInfo, + DepositAccount, + }, }; - let bytes = contract_info.encode(); - let contract_info_value_metadata: frame_metadata::v15::CustomValueMetadata< - PortableForm, - > = frame_metadata::v15::CustomValueMetadata { - ty: contract_info_ty_id, - value: contract_info.encode(), + use scale_info::{ + IntoPortable, + Path, }; + use subxt::metadata::DecodeWithMetadata; - let frame_metadata = frame_metadata::v15::RuntimeMetadataV15 { - types, - pallets: vec![], - extrinsic: frame_metadata::v15::ExtrinsicMetadata { - version: 0, - address_ty: unit_id, - call_ty: unit_id, - signature_ty: unit_id, - extra_ty: unit_id, - signed_extensions: vec![], - }, - ty: unit_id, - apis: vec![], - outer_enums: frame_metadata::v15::OuterEnums { - call_enum_ty: unit_id, - event_enum_ty: unit_id, - error_enum_ty: unit_id, - }, - custom: frame_metadata::v15::CustomMetadata { - map: BTreeMap::from_iter([( - "ContractInfoOf".to_string(), - contract_info_value_metadata, - )]), - }, + let metadata_bytes = std::fs::read("src/runtime_api/metadata_V11.scale").unwrap(); + let metadata = + subxt::metadata::types::Metadata::decode(&mut &*metadata_bytes).unwrap(); + + let contract_info_path = + Path::from_segments(vec!["pallet_contracts", "storage", "ContractInfo"]) + .unwrap() + .into_portable(&mut Default::default()); + + let contract_info_type_id = metadata + .types() + .types + .iter() + .enumerate() + .find_map(|(i, t)| { + if t.ty.path == contract_info_path { + Some(i) + } else { + None + } + }) + .expect("the contract info type must be present in the metadata"); + + let contract_info = ContractInfo { + trie_id: BoundedVec(vec![]), + deposit_account: DepositAccount(AccountId32([7u8; 32])), + code_hash: Default::default(), + storage_bytes: 1, + storage_items: 1, + storage_byte_deposit: 1, + storage_item_deposit: 1, + storage_base_deposit: 1, }; - let metadata: subxt::metadata::types::Metadata = frame_metadata - .try_into() - .expect("metadata conversion must work"); let contract_info_thunk = DecodedValueThunk::decode_with_metadata( - &mut &*bytes, - contract_info_ty_id.id, + &mut &*contract_info.encode(), + contract_info_type_id as u32, &metadata.into(), ) - .expect("contract info must be decoded"); + .expect("the contract info must be decoded"); + let deposit = get_deposit_account_id(&contract_info_thunk) - .expect("deposit account must be decoded from contract info"); + .expect("the deposit account must be decoded from contract info"); - assert_eq!(deposit.0, contract_info.deposit_account.0); + assert_eq!(deposit, contract_info.deposit_account.0); } } diff --git a/crates/extrinsics/src/runtime_api/metadata_v11.scale b/crates/extrinsics/src/runtime_api/metadata_v11.scale new file mode 100644 index 0000000000000000000000000000000000000000..d61d093bafa0b24b78b62fd55c6d59b54c6fc17e GIT binary patch literal 29868 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 RQMN=RbU!UVIQ%uO{x5i`?R@|M literal 0 HcmV?d00001 From 5d0d28bd582f8b8886eb2a94ae11e84465de77a1 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 10 Nov 2023 11:28:27 +0100 Subject: [PATCH 11/19] Updated tests --- crates/extrinsics/src/contract_info.rs | 101 ++++++++++++++++++++----- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 8e58baa6a..bd3cb02e0 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -75,7 +75,7 @@ async fn get_account_balance( Ok(data) } -/// Decode the account id from the contract info +/// Decode the deposit account from the contract info fn get_deposit_account_id(contract_info: &DecodedValueThunk) -> Result { #[derive(DecodeAsType)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] @@ -263,10 +263,34 @@ mod tests { DecodeWithMetadata, }; + /// Find the type index in the metadata. + fn get_metadata_type_index( + ident: &'static str, + module_path: &'static str, + metadata: &Metadata, + ) -> Result { + let contract_info_path = + Path::new(ident, module_path).into_portable(&mut Default::default()); + + metadata + .types() + .types + .iter() + .enumerate() + .find_map(|(i, t)| { + if t.ty.path == contract_info_path { + Some(i) + } else { + None + } + }) + .ok_or(anyhow!("Type not found")) + } + #[test] fn deposit_decode_works() { #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata_V11.scale")] - pub mod api_v11 {} + mod api_v11 {} use api_v11::runtime_types::{ bounded_collections::bounded_vec::BoundedVec, @@ -278,25 +302,12 @@ mod tests { let metadata_bytes = std::fs::read("src/runtime_api/metadata_V11.scale").unwrap(); let metadata = Metadata::decode(&mut &*metadata_bytes).unwrap(); - - let contract_info_path = - Path::from_segments(vec!["pallet_contracts", "storage", "ContractInfo"]) - .unwrap() - .into_portable(&mut Default::default()); - - let contract_info_type_id = metadata - .types() - .types - .iter() - .enumerate() - .find_map(|(i, t)| { - if t.ty.path == contract_info_path { - Some(i) - } else { - None - } - }) - .expect("the contract info type must be present in the metadata"); + let contract_info_type_id = get_metadata_type_index( + "ContractInfo", + "pallet_contracts::storage", + &metadata, + ) + .expect("the contract info type must be present in the metadata"); let contract_info = ContractInfo { trie_id: BoundedVec(vec![]), @@ -321,4 +332,52 @@ mod tests { assert_eq!(deposit, contract_info.deposit_account.0); } + + #[test] + fn deposit_decode_fails() { + #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata.scale")] + mod api_v15 {} + + use api_v15::runtime_types::{ + bounded_collections::{ + bounded_btree_map::BoundedBTreeMap, + bounded_vec::BoundedVec, + }, + pallet_contracts::storage::ContractInfo, + }; + + let metadata_bytes = std::fs::read("src/runtime_api/metadata_V11.scale").unwrap(); + let metadata = Metadata::decode(&mut &*metadata_bytes).unwrap(); + let contract_info_type_id = get_metadata_type_index( + "ContractInfo", + "pallet_contracts::storage", + &metadata, + ) + .expect("the contract info type must be present in the metadata"); + + let contract_info = ContractInfo { + trie_id: BoundedVec(vec![]), + code_hash: Default::default(), + storage_bytes: 1, + storage_items: 1, + storage_byte_deposit: 1, + storage_item_deposit: 1, + storage_base_deposit: 1, + delegate_dependencies: BoundedBTreeMap(vec![]), + }; + + let contract_info_thunk = DecodedValueThunk::decode_with_metadata( + &mut &*contract_info.encode(), + contract_info_type_id as u32, + &metadata.into(), + ) + .expect("the contract info must be decoded"); + + let res = get_deposit_account_id(&contract_info_thunk) + .expect_err("decoding the deposit account must fail"); + assert_eq!( + res.to_string(), + "Error at : Error decoding bytes given the type ID and registry provided: Not enough data to fill buffer" + ); + } } From 18474d873570afdd6ff8ba32bfc64a7f445a2a6d Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 10 Nov 2023 12:29:52 +0100 Subject: [PATCH 12/19] Fixed tests --- crates/extrinsics/src/contract_info.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index bd3cb02e0..809472d83 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -289,7 +289,8 @@ mod tests { #[test] fn deposit_decode_works() { - #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata_V11.scale")] + // This version of metadata includes the deposit_account field in ContractInfo + #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata_v11.scale")] mod api_v11 {} use api_v11::runtime_types::{ @@ -300,8 +301,10 @@ mod tests { }, }; - let metadata_bytes = std::fs::read("src/runtime_api/metadata_V11.scale").unwrap(); - let metadata = Metadata::decode(&mut &*metadata_bytes).unwrap(); + let metadata_bytes = std::fs::read("src/runtime_api/metadata_v11.scale") + .expect("the metadata must be present"); + let metadata = + Metadata::decode(&mut &*metadata_bytes).expect("the metadata must decode"); let contract_info_type_id = get_metadata_type_index( "ContractInfo", "pallet_contracts::storage", @@ -335,6 +338,8 @@ mod tests { #[test] fn deposit_decode_fails() { + // This version of metadata does not include the deposit_account field in + // ContractInfo #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata.scale")] mod api_v15 {} @@ -346,8 +351,10 @@ mod tests { pallet_contracts::storage::ContractInfo, }; - let metadata_bytes = std::fs::read("src/runtime_api/metadata_V11.scale").unwrap(); - let metadata = Metadata::decode(&mut &*metadata_bytes).unwrap(); + let metadata_bytes = std::fs::read("src/runtime_api/metadata.scale") + .expect("the metadata must be present"); + let metadata = + Metadata::decode(&mut &*metadata_bytes).expect("the metadata must decode"); let contract_info_type_id = get_metadata_type_index( "ContractInfo", "pallet_contracts::storage", @@ -377,7 +384,7 @@ mod tests { .expect_err("decoding the deposit account must fail"); assert_eq!( res.to_string(), - "Error at : Error decoding bytes given the type ID and registry provided: Not enough data to fill buffer" + "Error at : Field deposit_account does not exist in our encoded data" ); } } From 1b743ddc4832886f7025832a3ba80fbdc7651b46 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 10 Nov 2023 14:05:27 +0100 Subject: [PATCH 13/19] Renamed type --- crates/extrinsics/src/contract_info.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 809472d83..d47bd46ab 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -296,7 +296,7 @@ mod tests { use api_v11::runtime_types::{ bounded_collections::bounded_vec::BoundedVec, pallet_contracts::storage::{ - ContractInfo, + ContractInfo as ContractInfoV11, DepositAccount, }, }; @@ -312,7 +312,7 @@ mod tests { ) .expect("the contract info type must be present in the metadata"); - let contract_info = ContractInfo { + let contract_info = ContractInfoV11 { trie_id: BoundedVec(vec![]), deposit_account: DepositAccount(AccountId32([7u8; 32])), code_hash: Default::default(), @@ -348,7 +348,7 @@ mod tests { bounded_btree_map::BoundedBTreeMap, bounded_vec::BoundedVec, }, - pallet_contracts::storage::ContractInfo, + pallet_contracts::storage::ContractInfo as ContractInfoV15, }; let metadata_bytes = std::fs::read("src/runtime_api/metadata.scale") @@ -362,7 +362,7 @@ mod tests { ) .expect("the contract info type must be present in the metadata"); - let contract_info = ContractInfo { + let contract_info = ContractInfoV15 { trie_id: BoundedVec(vec![]), code_hash: Default::default(), storage_bytes: 1, From 572b797b2ff4d14606da33028263b1ce0519a08c Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 11:55:10 +0100 Subject: [PATCH 14/19] Code refactored and updated unit tests --- crates/extrinsics/src/contract_info.rs | 214 +++++++++++++++++-------- 1 file changed, 143 insertions(+), 71 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index d47bd46ab..36c9d9a64 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -41,13 +41,6 @@ use subxt::{ utils::AccountId32, }; -#[derive(DecodeAsType, Debug)] -#[decode_as_type(crate_path = "subxt::ext::scale_decode")] -struct AccountData { - pub free: Balance, - pub reserved: Balance, -} - /// Return the account data for an account ID. async fn get_account_balance( account: &AccountId32, @@ -65,28 +58,10 @@ async fn get_account_balance( .await? .ok_or_else(|| anyhow::anyhow!("Failed to fetch account data"))?; - #[derive(DecodeAsType, Debug)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - struct AccountInfo { - data: AccountData, - } - let data = account.as_type::()?.data; Ok(data) } -/// Decode the deposit account from the contract info -fn get_deposit_account_id(contract_info: &DecodedValueThunk) -> Result { - #[derive(DecodeAsType)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - struct DepositAccount { - deposit_account: AccountId32, - } - - let account = contract_info.as_type::()?; - Ok(account.deposit_account) -} - /// Fetch the contract info from the storage using the provided client. pub async fn fetch_contract_info( contract: &AccountId32, @@ -100,7 +75,7 @@ pub async fn fetch_contract_info( "ContractInfoOf", vec![Value::from_bytes(contract)], ); - let contract_info = client + let contract_info_value = client .storage() .at(best_block) .fetch(&contract_info_address) @@ -111,43 +86,75 @@ pub async fn fetch_contract_info( contract ) })?; - #[derive(DecodeAsType, Debug)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - pub struct BoundedVec(pub ::std::vec::Vec); - #[derive(DecodeAsType, Debug)] - #[decode_as_type(crate_path = "subxt::ext::scale_decode")] - struct ContractInfoOf { - trie_id: BoundedVec, - code_hash: CodeHash, - storage_items: u32, - storage_item_deposit: Balance, + + let contract_info_raw = ContractInfoRaw::new(contract, contract_info_value)?; + let deposit_account = match contract_info_raw { + ContractInfoRaw::MainAccountDeposit(ref account, _) + | ContractInfoRaw::SecondaryAccountDeposit(ref account, _) => account, + }; + + let deposit_account_data = get_account_balance(deposit_account, rpc, client).await?; + Ok(contract_info_raw.into_contract_info(deposit_account_data)) +} + +/// Enum representing different types of contract info, distinguishing between +/// main account deposit and secondary account deposit. +pub enum ContractInfoRaw { + MainAccountDeposit(AccountId32, ContractInfoOf), + SecondaryAccountDeposit(AccountId32, ContractInfoOf), +} + +impl ContractInfoRaw { + /// Create a new instance of `ContractInfoRaw` based on the provided contract and + /// contract info value. Determines whether it's a main or secondary account deposit. + pub fn new( + contract: &AccountId32, + contract_info_value: DecodedValueThunk, + ) -> Result { + let info = contract_info_value.as_type::()?; + // Pallet-contracts [>=10, <15] store the contract's deposit as a free balance + // in a secondary account (deposit account). Other versions store it as + // reserved balance on the main contract's account. If the + // `deposit_account` field is present in a contract info structure, + // the contract's deposit is in this account. + match Self::get_deposit_account_id(&contract_info_value) { + Ok(deposit_account) => { + Ok(Self::SecondaryAccountDeposit(deposit_account, info)) + } + Err(_) => Ok(Self::MainAccountDeposit(contract.clone(), info)), + } } - // Pallet-contracts [>=10, <15] store the contract's deposit as a free balance - // in a secondary account (deposit account). Other versions store it as - // reserved balance on the main contract's account. If the - // `deposit_account` field is present in a contract info structure, - // the contract's deposit is in this account. - let total_balance: Balance = match get_deposit_account_id(&contract_info) { - Ok(deposit_account) => { - get_account_balance(&deposit_account, rpc, client) - .await? - .free + /// Convert `ContractInfoRaw` to `ContractInfo` + pub fn into_contract_info(self, deposit: AccountData) -> ContractInfo { + let total_deposit = match self { + Self::MainAccountDeposit(_, _) => deposit.free, + + Self::SecondaryAccountDeposit(_, _) => deposit.reserved, + }; + + match self { + Self::MainAccountDeposit(_, ref info) + | Self::SecondaryAccountDeposit(_, ref info) => { + ContractInfo { + trie_id: hex::encode(&info.trie_id.0), + code_hash: info.code_hash, + storage_items: info.storage_items, + storage_items_deposit: info.storage_item_deposit, + storage_total_deposit: total_deposit, + } + } } - Err(_) => get_account_balance(contract, rpc, client).await?.reserved, - }; + } - let info = contract_info.as_type::()?; - Ok(ContractInfo { - trie_id: hex::encode(info.trie_id.0), - code_hash: info.code_hash, - storage_items: info.storage_items, - storage_items_deposit: info.storage_item_deposit, - storage_total_deposit: total_balance, - }) + /// Decode the deposit account from the contract info + fn get_deposit_account_id(contract_info: &DecodedValueThunk) -> Result { + let account = contract_info.as_type::()?; + Ok(account.deposit_account) + } } -#[derive(serde::Serialize)] +#[derive(Debug, serde::Serialize, std::cmp::PartialEq)] pub struct ContractInfo { trie_id: String, code_hash: CodeHash, @@ -250,6 +257,43 @@ pub async fn fetch_all_contracts( Ok(contract_accounts) } +/// A struct used in the storage reads to access account info. +#[derive(DecodeAsType, Debug)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +struct AccountInfo { + data: AccountData, +} + +/// A struct used in the storage reads to access account data. +#[derive(Clone, Debug, DecodeAsType)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +pub struct AccountData { + free: Balance, + reserved: Balance, +} + +/// A struct representing `Vec`` used in the storage reads. +#[derive(Debug, DecodeAsType)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +struct BoundedVec(pub ::std::vec::Vec); + +/// A struct used in the storage reads to access contract info. +#[derive(Debug, DecodeAsType)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +pub struct ContractInfoOf { + trie_id: BoundedVec, + code_hash: CodeHash, + storage_items: u32, + storage_item_deposit: Balance, +} + +/// A struct used in storage reads to access the deposit account from contract info. +#[derive(Debug, DecodeAsType)] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +struct DepositAccount { + deposit_account: AccountId32, +} + #[cfg(test)] mod tests { use super::*; @@ -263,7 +307,7 @@ mod tests { DecodeWithMetadata, }; - /// Find the type index in the metadata. + // Find the type index in the metadata. fn get_metadata_type_index( ident: &'static str, module_path: &'static str, @@ -288,7 +332,7 @@ mod tests { } #[test] - fn deposit_decode_works() { + fn contract_info_v11_decode_works() { // This version of metadata includes the deposit_account field in ContractInfo #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata_v11.scale")] mod api_v11 {} @@ -312,7 +356,7 @@ mod tests { ) .expect("the contract info type must be present in the metadata"); - let contract_info = ContractInfoV11 { + let contract_info_v11 = ContractInfoV11 { trie_id: BoundedVec(vec![]), deposit_account: DepositAccount(AccountId32([7u8; 32])), code_hash: Default::default(), @@ -324,20 +368,35 @@ mod tests { }; let contract_info_thunk = DecodedValueThunk::decode_with_metadata( - &mut &*contract_info.encode(), + &mut &*contract_info_v11.encode(), contract_info_type_id as u32, &metadata.into(), ) .expect("the contract info must be decoded"); - let deposit = get_deposit_account_id(&contract_info_thunk) - .expect("the deposit account must be decoded from contract info"); + let contract = AccountId32([0u8; 32]); + let contract_info_raw = ContractInfoRaw::new(&contract, contract_info_thunk) + .expect("the conatract info raw must be created"); + let account_data = AccountData { + free: 1, + reserved: 10, + }; - assert_eq!(deposit, contract_info.deposit_account.0); + let contract_info = contract_info_raw.into_contract_info(account_data.clone()); + assert_eq!( + contract_info, + ContractInfo { + trie_id: hex::encode(contract_info_v11.trie_id.0), + code_hash: contract_info_v11.code_hash, + storage_items: contract_info_v11.storage_items, + storage_items_deposit: contract_info_v11.storage_item_deposit, + storage_total_deposit: account_data.reserved, + } + ); } #[test] - fn deposit_decode_fails() { + fn contract_info_v15_decode_works() { // This version of metadata does not include the deposit_account field in // ContractInfo #[subxt::subxt(runtime_metadata_path = "src/runtime_api/metadata.scale")] @@ -362,7 +421,7 @@ mod tests { ) .expect("the contract info type must be present in the metadata"); - let contract_info = ContractInfoV15 { + let contract_info_v15 = ContractInfoV15 { trie_id: BoundedVec(vec![]), code_hash: Default::default(), storage_bytes: 1, @@ -374,17 +433,30 @@ mod tests { }; let contract_info_thunk = DecodedValueThunk::decode_with_metadata( - &mut &*contract_info.encode(), + &mut &*contract_info_v15.encode(), contract_info_type_id as u32, &metadata.into(), ) .expect("the contract info must be decoded"); - let res = get_deposit_account_id(&contract_info_thunk) - .expect_err("decoding the deposit account must fail"); + let contract = AccountId32([0u8; 32]); + let contract_info_raw = ContractInfoRaw::new(&contract, contract_info_thunk) + .expect("the conatract info raw must be created"); + let account_data = AccountData { + free: 1, + reserved: 10, + }; + + let contract_info = contract_info_raw.into_contract_info(account_data.clone()); assert_eq!( - res.to_string(), - "Error at : Field deposit_account does not exist in our encoded data" + contract_info, + ContractInfo { + trie_id: hex::encode(contract_info_v15.trie_id.0), + code_hash: contract_info_v15.code_hash, + storage_items: contract_info_v15.storage_items, + storage_items_deposit: contract_info_v15.storage_item_deposit, + storage_total_deposit: account_data.free, + } ); } } From f1434217497b40b7aad8909f58b47aaf23ebe528 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 12:25:03 +0100 Subject: [PATCH 15/19] Code cleanup --- crates/extrinsics/src/contract_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 36c9d9a64..ca44b2733 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -154,7 +154,7 @@ impl ContractInfoRaw { } } -#[derive(Debug, serde::Serialize, std::cmp::PartialEq)] +#[derive(Debug, PartialEq, serde::Serialize)] pub struct ContractInfo { trie_id: String, code_hash: CodeHash, From 7b149cb27152251fb19a22bf2fa73194b4c17083 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 13:26:00 +0100 Subject: [PATCH 16/19] Make internal structs private --- crates/extrinsics/src/contract_info.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index ca44b2733..2614d9240 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -99,7 +99,7 @@ pub async fn fetch_contract_info( /// Enum representing different types of contract info, distinguishing between /// main account deposit and secondary account deposit. -pub enum ContractInfoRaw { +enum ContractInfoRaw { MainAccountDeposit(AccountId32, ContractInfoOf), SecondaryAccountDeposit(AccountId32, ContractInfoOf), } @@ -267,7 +267,7 @@ struct AccountInfo { /// A struct used in the storage reads to access account data. #[derive(Clone, Debug, DecodeAsType)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] -pub struct AccountData { +struct AccountData { free: Balance, reserved: Balance, } @@ -280,7 +280,7 @@ struct BoundedVec(pub ::std::vec::Vec); /// A struct used in the storage reads to access contract info. #[derive(Debug, DecodeAsType)] #[decode_as_type(crate_path = "subxt::ext::scale_decode")] -pub struct ContractInfoOf { +struct ContractInfoOf { trie_id: BoundedVec, code_hash: CodeHash, storage_items: u32, From 247aed6b9772c12d9c45f13d63c7b4766ac0802d Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 14:40:57 +0100 Subject: [PATCH 17/19] Code refactored --- crates/extrinsics/src/contract_info.rs | 69 ++++++++++++++------------ 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 2614d9240..7b72a99cb 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -87,11 +87,8 @@ pub async fn fetch_contract_info( ) })?; - let contract_info_raw = ContractInfoRaw::new(contract, contract_info_value)?; - let deposit_account = match contract_info_raw { - ContractInfoRaw::MainAccountDeposit(ref account, _) - | ContractInfoRaw::SecondaryAccountDeposit(ref account, _) => account, - }; + let contract_info_raw = ContractInfoRaw::new(contract.clone(), contract_info_value)?; + let deposit_account = contract_info_raw.get_deposit_account(); let deposit_account_data = get_account_balance(deposit_account, rpc, client).await?; Ok(contract_info_raw.into_contract_info(deposit_account_data)) @@ -99,19 +96,20 @@ pub async fn fetch_contract_info( /// Enum representing different types of contract info, distinguishing between /// main account deposit and secondary account deposit. -enum ContractInfoRaw { - MainAccountDeposit(AccountId32, ContractInfoOf), - SecondaryAccountDeposit(AccountId32, ContractInfoOf), +struct ContractInfoRaw { + deposit_account: AccountId32, + contract_info: ContractInfoOf, + deposit_on_main_account: bool, } impl ContractInfoRaw { /// Create a new instance of `ContractInfoRaw` based on the provided contract and /// contract info value. Determines whether it's a main or secondary account deposit. pub fn new( - contract: &AccountId32, + contract_account: AccountId32, contract_info_value: DecodedValueThunk, ) -> Result { - let info = contract_info_value.as_type::()?; + let contract_info = contract_info_value.as_type::()?; // Pallet-contracts [>=10, <15] store the contract's deposit as a free balance // in a secondary account (deposit account). Other versions store it as // reserved balance on the main contract's account. If the @@ -119,31 +117,40 @@ impl ContractInfoRaw { // the contract's deposit is in this account. match Self::get_deposit_account_id(&contract_info_value) { Ok(deposit_account) => { - Ok(Self::SecondaryAccountDeposit(deposit_account, info)) + Ok(Self { + deposit_account, + contract_info, + deposit_on_main_account: false, + }) + } + Err(_) => { + Ok(Self { + deposit_account: contract_account, + contract_info, + deposit_on_main_account: true, + }) } - Err(_) => Ok(Self::MainAccountDeposit(contract.clone(), info)), } } + pub fn get_deposit_account(&self) -> &AccountId32 { + &self.deposit_account + } + /// Convert `ContractInfoRaw` to `ContractInfo` pub fn into_contract_info(self, deposit: AccountData) -> ContractInfo { - let total_deposit = match self { - Self::MainAccountDeposit(_, _) => deposit.free, - - Self::SecondaryAccountDeposit(_, _) => deposit.reserved, + let total_deposit = if self.deposit_on_main_account { + deposit.reserved + } else { + deposit.free }; - match self { - Self::MainAccountDeposit(_, ref info) - | Self::SecondaryAccountDeposit(_, ref info) => { - ContractInfo { - trie_id: hex::encode(&info.trie_id.0), - code_hash: info.code_hash, - storage_items: info.storage_items, - storage_items_deposit: info.storage_item_deposit, - storage_total_deposit: total_deposit, - } - } + ContractInfo { + trie_id: hex::encode(&self.contract_info.trie_id.0), + code_hash: self.contract_info.code_hash, + storage_items: self.contract_info.storage_items, + storage_items_deposit: self.contract_info.storage_item_deposit, + storage_total_deposit: total_deposit, } } @@ -375,7 +382,7 @@ mod tests { .expect("the contract info must be decoded"); let contract = AccountId32([0u8; 32]); - let contract_info_raw = ContractInfoRaw::new(&contract, contract_info_thunk) + let contract_info_raw = ContractInfoRaw::new(contract, contract_info_thunk) .expect("the conatract info raw must be created"); let account_data = AccountData { free: 1, @@ -390,7 +397,7 @@ mod tests { code_hash: contract_info_v11.code_hash, storage_items: contract_info_v11.storage_items, storage_items_deposit: contract_info_v11.storage_item_deposit, - storage_total_deposit: account_data.reserved, + storage_total_deposit: account_data.free, } ); } @@ -440,7 +447,7 @@ mod tests { .expect("the contract info must be decoded"); let contract = AccountId32([0u8; 32]); - let contract_info_raw = ContractInfoRaw::new(&contract, contract_info_thunk) + let contract_info_raw = ContractInfoRaw::new(contract, contract_info_thunk) .expect("the conatract info raw must be created"); let account_data = AccountData { free: 1, @@ -455,7 +462,7 @@ mod tests { code_hash: contract_info_v15.code_hash, storage_items: contract_info_v15.storage_items, storage_items_deposit: contract_info_v15.storage_item_deposit, - storage_total_deposit: account_data.free, + storage_total_deposit: account_data.reserved, } ); } From 5bc3432b8be593454cb323a4ce33f776bbe75a8a Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 14:47:57 +0100 Subject: [PATCH 18/19] Fixed comment --- crates/extrinsics/src/contract_info.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 7b72a99cb..6e5c3ef26 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -94,8 +94,7 @@ pub async fn fetch_contract_info( Ok(contract_info_raw.into_contract_info(deposit_account_data)) } -/// Enum representing different types of contract info, distinguishing between -/// main account deposit and secondary account deposit. +/// Struct representing contract info, supporting deposit on either the main or secondary account. struct ContractInfoRaw { deposit_account: AccountId32, contract_info: ContractInfoOf, From 974bc0b1a008103d0cdfed0000811ea0044d627e Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 15:10:38 +0100 Subject: [PATCH 19/19] Fix fmt --- crates/extrinsics/src/contract_info.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 6e5c3ef26..6294e5fd9 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -94,7 +94,8 @@ pub async fn fetch_contract_info( Ok(contract_info_raw.into_contract_info(deposit_account_data)) } -/// Struct representing contract info, supporting deposit on either the main or secondary account. +/// Struct representing contract info, supporting deposit on either the main or secondary +/// account. struct ContractInfoRaw { deposit_account: AccountId32, contract_info: ContractInfoOf,