Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump `subxt` to `0.32.0` - [#1352](https://github.com/paritytech/cargo-contract/pull/1352)
- Remove check for compatible `scale` and `scale-info` versions - [#1370](https://github.com/paritytech/cargo-contract/pull/1370)
- Add workspace support -[#1358](https://github.com/paritytech/cargo-contract/pull/1358)
- Add `Storage Total Deposit` to `info` command output - [#1347](https://github.com/paritytech/cargo-contract/pull/1347)

## [4.0.0-alpha]

Expand Down
13 changes: 5 additions & 8 deletions crates/cargo-contract/src/cmd/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,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?;

let wasm_code = fetch_wasm_code(&client, &rpc, info_to_json.code_hash())
.await?
Expand Down Expand Up @@ -154,7 +149,8 @@ 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,
}

Expand All @@ -168,7 +164,8 @@ 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,
}
}
Expand Down
11 changes: 8 additions & 3 deletions crates/cargo-contract/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,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.
Expand Down Expand Up @@ -230,8 +230,13 @@ pub fn basic_display_format_extended_contract_info(info: &ExtendedContractInfo)
MAX_KEY_COL_WIDTH
);
name_value_println!(
"Storage Deposit",
format!("{:?}", info.storage_item_deposit),
"Storage Items Deposit",
format!("{:?}", info.storage_items_deposit),
MAX_KEY_COL_WIDTH
);
name_value_println!(
STORAGE_DEPOSIT_KEY,
format!("{:?}", info.storage_total_deposit),
MAX_KEY_COL_WIDTH
);
name_value_println!(
Expand Down
267 changes: 247 additions & 20 deletions crates/extrinsics/src/contract_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,45 +32,128 @@ use scale::Decode;
use std::option::Option;
use subxt::{
backend::legacy::LegacyRpcMethods,
dynamic::DecodedValueThunk,
ext::{
scale_decode::DecodeAsType,
scale_value::Value,
},
storage::dynamic,
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,
rpc: &LegacyRpcMethods<DefaultConfig>,
client: &Client,
) -> Result<AccountData> {
let storage_query =
subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
let best_block = get_best_block(rpc).await?;

let account = client
.storage()
.at(best_block)
.fetch(&storage_query)
.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::<AccountInfo>()?.data;
Ok(data)
}

/// Decode the deposit account from the contract info
fn get_deposit_account_id(contract_info: &DecodedValueThunk) -> Result<AccountId32> {
#[derive(DecodeAsType)]
#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
struct DepositAccount {
deposit_account: AccountId32,
}

let account = contract_info.as_type::<DepositAccount>()?;
Ok(account.deposit_account)
}

/// Fetch the contract info from the storage using the provided client.
pub async fn fetch_contract_info(
contract: &AccountId32,
rpc: &LegacyRpcMethods<DefaultConfig>,
client: &Client,
) -> Result<Option<ContractInfo>> {
let info_contract_call = api::storage().contracts().contract_info_of(contract);

) -> Result<ContractInfo> {
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?;
.fetch(&contract_info_address)
.await?
.ok_or_else(|| {
anyhow!(
"No contract information was found for account id {}",
contract
)
})?;
#[derive(DecodeAsType, Debug)]
#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
pub struct BoundedVec<T>(pub ::std::vec::Vec<T>);
#[derive(DecodeAsType, Debug)]
#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
struct ContractInfoOf {
trie_id: BoundedVec<u8>,
code_hash: CodeHash,
storage_items: u32,
storage_item_deposit: Balance,
}

match contract_info_of {
Some(info_result) => {
let convert_trie_id = hex::encode(info_result.trie_id.0);
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,
}))
// 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
}
None => Ok(None),
}
Err(_) => get_account_balance(contract, rpc, client).await?.reserved,
};

let info = contract_info.as_type::<ContractInfoOf>()?;
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,
})
}

#[derive(serde::Serialize)]
pub struct ContractInfo {
trie_id: String,
code_hash: CodeHash,
storage_items: u32,
storage_item_deposit: Balance,
storage_items_deposit: Balance,
storage_total_deposit: Balance,
}

impl ContractInfo {
Expand All @@ -95,8 +178,13 @@ 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.
pub fn storage_total_deposit(&self) -> Balance {
self.storage_total_deposit
}
}

Expand Down Expand Up @@ -161,3 +249,142 @@ pub async fn fetch_all_contracts(

Ok(contract_accounts)
}

#[cfg(test)]
mod tests {
use super::*;
use scale::Encode;
use scale_info::{
IntoPortable,
Path,
};
use subxt::metadata::{
types::Metadata,
DecodeWithMetadata,
};

/// Find the type index in the metadata.
fn get_metadata_type_index(
ident: &'static str,
module_path: &'static str,
metadata: &Metadata,
) -> Result<usize> {
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() {
// 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::{
bounded_collections::bounded_vec::BoundedVec,
pallet_contracts::storage::{
ContractInfo as ContractInfoV11,
DepositAccount,
},
};

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",
&metadata,
)
.expect("the contract info type must be present in the metadata");

let contract_info = ContractInfoV11 {
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 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 deposit = get_deposit_account_id(&contract_info_thunk)
.expect("the deposit account must be decoded from contract info");

assert_eq!(deposit, contract_info.deposit_account.0);
}

#[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 {}

use api_v15::runtime_types::{
bounded_collections::{
bounded_btree_map::BoundedBTreeMap,
bounded_vec::BoundedVec,
},
pallet_contracts::storage::ContractInfo as ContractInfoV15,
};

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",
&metadata,
)
.expect("the contract info type must be present in the metadata");

let contract_info = ContractInfoV15 {
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 : Field deposit_account does not exist in our encoded data"
);
}
}
Binary file not shown.