1use super::get_best_block;
18use anyhow::{
19 anyhow,
20 Result,
21};
22use contract_metadata::byte_str::serialize_as_byte_str;
23use std::fmt::{
24 Display,
25 Formatter,
26};
27
28use ink_env::Environment;
29use scale::Decode;
30use std::option::Option;
31use subxt::{
32 backend::legacy::LegacyRpcMethods,
33 dynamic::DecodedValueThunk,
34 ext::{
35 scale_decode::{
36 DecodeAsType,
37 IntoVisitor,
38 },
39 scale_value::Value,
40 },
41 storage::dynamic,
42 Config,
43 OnlineClient,
44};
45
46async fn get_account_balance<C: Config, E: Environment>(
48 account: &C::AccountId,
49 rpc: &LegacyRpcMethods<C>,
50 client: &OnlineClient<C>,
51) -> Result<AccountData<E::Balance>>
52where
53 C::AccountId: AsRef<[u8]>,
54 E::Balance: IntoVisitor,
55{
56 let storage_query =
57 subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
58 let best_block = get_best_block(rpc).await?;
59
60 let account = client
61 .storage()
62 .at(best_block)
63 .fetch(&storage_query)
64 .await?
65 .ok_or_else(|| anyhow::anyhow!("Failed to fetch account data"))?;
66
67 let data = account.as_type::<AccountInfo<E::Balance>>()?.data;
68 Ok(data)
69}
70
71pub async fn fetch_contract_info<C: Config, E: Environment>(
73 contract: &C::AccountId,
74 rpc: &LegacyRpcMethods<C>,
75 client: &OnlineClient<C>,
76) -> Result<ContractInfo<C::Hash, E::Balance>>
77where
78 C::AccountId: AsRef<[u8]> + Display + IntoVisitor,
79 C::Hash: IntoVisitor,
80 E::Balance: IntoVisitor,
81{
82 let best_block = get_best_block(rpc).await?;
83
84 let contract_info_address = dynamic(
85 "Contracts",
86 "ContractInfoOf",
87 vec![Value::from_bytes(contract)],
88 );
89 let contract_info_value = client
90 .storage()
91 .at(best_block)
92 .fetch(&contract_info_address)
93 .await?
94 .ok_or_else(|| {
95 anyhow!(
96 "No contract information was found for account id {}",
97 contract
98 )
99 })?;
100
101 let contract_info_raw =
102 ContractInfoRaw::<C, E>::new(contract.clone(), contract_info_value)?;
103 let deposit_account = contract_info_raw.get_deposit_account();
104
105 let deposit_account_data =
106 get_account_balance::<C, E>(deposit_account, rpc, client).await?;
107 Ok(contract_info_raw.into_contract_info(deposit_account_data))
108}
109
110struct ContractInfoRaw<C: Config, E: Environment> {
113 deposit_account: C::AccountId,
114 contract_info: ContractInfoOf<C::Hash, E::Balance>,
115 deposit_on_main_account: bool,
116}
117
118impl<C: Config, E: Environment> ContractInfoRaw<C, E>
119where
120 C::AccountId: IntoVisitor,
121 C::Hash: IntoVisitor,
122 E::Balance: IntoVisitor,
123{
124 pub fn new(
127 contract_account: C::AccountId,
128 contract_info_value: DecodedValueThunk,
129 ) -> Result<Self> {
130 let contract_info =
131 contract_info_value.as_type::<ContractInfoOf<C::Hash, E::Balance>>()?;
132 match Self::get_deposit_account_id(&contract_info_value) {
138 Ok(deposit_account) => {
139 Ok(Self {
140 deposit_account,
141 contract_info,
142 deposit_on_main_account: false,
143 })
144 }
145 Err(_) => {
146 Ok(Self {
147 deposit_account: contract_account,
148 contract_info,
149 deposit_on_main_account: true,
150 })
151 }
152 }
153 }
154
155 pub fn get_deposit_account(&self) -> &C::AccountId {
156 &self.deposit_account
157 }
158
159 pub fn into_contract_info(
161 self,
162 deposit: AccountData<E::Balance>,
163 ) -> ContractInfo<C::Hash, E::Balance> {
164 let total_deposit = if self.deposit_on_main_account {
165 deposit.reserved
166 } else {
167 deposit.free
168 };
169
170 ContractInfo {
171 trie_id: self.contract_info.trie_id.0.into(),
172 code_hash: self.contract_info.code_hash,
173 storage_items: self.contract_info.storage_items,
174 storage_items_deposit: self.contract_info.storage_item_deposit,
175 storage_total_deposit: total_deposit,
176 }
177 }
178
179 fn get_deposit_account_id(contract_info: &DecodedValueThunk) -> Result<C::AccountId> {
181 let account = contract_info.as_type::<DepositAccount<C::AccountId>>()?;
182 Ok(account.deposit_account)
183 }
184}
185
186#[derive(Debug, PartialEq, serde::Serialize)]
187pub struct ContractInfo<Hash, Balance> {
188 trie_id: TrieId,
189 code_hash: Hash,
190 storage_items: u32,
191 storage_items_deposit: Balance,
192 storage_total_deposit: Balance,
193}
194
195impl<Hash, Balance> ContractInfo<Hash, Balance>
196where
197 Hash: serde::Serialize,
198 Balance: serde::Serialize + Copy,
199{
200 pub fn to_json(&self) -> Result<String> {
202 Ok(serde_json::to_string_pretty(self)?)
203 }
204
205 pub fn trie_id(&self) -> &TrieId {
207 &self.trie_id
208 }
209
210 pub fn code_hash(&self) -> &Hash {
212 &self.code_hash
213 }
214
215 pub fn storage_items(&self) -> u32 {
217 self.storage_items
218 }
219
220 pub fn storage_items_deposit(&self) -> Balance {
222 self.storage_items_deposit
223 }
224
225 pub fn storage_total_deposit(&self) -> Balance {
227 self.storage_total_deposit
228 }
229}
230
231#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
233pub struct TrieId(#[serde(serialize_with = "serialize_as_byte_str")] Vec<u8>);
234
235impl TrieId {
236 pub fn to_hex(&self) -> String {
238 format!("0x{}", hex::encode(&self.0))
239 }
240}
241
242impl From<Vec<u8>> for TrieId {
243 fn from(raw: Vec<u8>) -> Self {
244 Self(raw)
245 }
246}
247
248impl AsRef<[u8]> for TrieId {
249 fn as_ref(&self) -> &[u8] {
250 &self.0
251 }
252}
253
254impl Display for TrieId {
255 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
256 write!(f, "{}", self.to_hex())
257 }
258}
259
260pub async fn fetch_wasm_code<C: Config>(
262 client: &OnlineClient<C>,
263 rpc: &LegacyRpcMethods<C>,
264 hash: &C::Hash,
265) -> Result<Vec<u8>>
266where
267 C::Hash: AsRef<[u8]> + Display + IntoVisitor,
268{
269 let best_block = get_best_block(rpc).await?;
270
271 let pristine_code_address =
272 dynamic("Contracts", "PristineCode", vec![Value::from_bytes(hash)]);
273 let pristine_code = client
274 .storage()
275 .at(best_block)
276 .fetch(&pristine_code_address)
277 .await?
278 .ok_or_else(|| anyhow!("No WASM code was found for code hash {}", hash))?;
279 let pristine_code = pristine_code
280 .as_type::<BoundedVec<u8>>()
281 .map_err(|e| anyhow!("Contract wasm code could not be parsed: {e}"));
282 pristine_code.map(|v| v.0)
283}
284
285fn parse_contract_account_address<C: Config>(
288 storage_contract_account_key: &[u8],
289 storage_contract_root_key_len: usize,
290) -> Result<C::AccountId>
291where
292 C::AccountId: Decode,
293{
294 let mut account = storage_contract_account_key
297 .get(storage_contract_root_key_len + 8..)
298 .ok_or(anyhow!("Unexpected storage key size"))?;
299 Decode::decode(&mut account)
300 .map_err(|err| anyhow!("AccountId deserialization error: {}", err))
301}
302
303pub async fn fetch_all_contracts<C: Config>(
305 client: &OnlineClient<C>,
306 rpc: &LegacyRpcMethods<C>,
307) -> Result<Vec<C::AccountId>>
308where
309 C::AccountId: Decode,
310{
311 let best_block = get_best_block(rpc).await?;
312 let root_key =
313 subxt::dynamic::storage("Contracts", "ContractInfoOf", ()).to_root_bytes();
314 let mut keys = client
315 .storage()
316 .at(best_block)
317 .fetch_raw_keys(root_key.clone())
318 .await?;
319
320 let mut contract_accounts = Vec::new();
321 while let Some(result) = keys.next().await {
322 let key = result?;
323 let contract_account = parse_contract_account_address::<C>(&key, root_key.len())?;
324 contract_accounts.push(contract_account);
325 }
326
327 Ok(contract_accounts)
328}
329
330#[derive(DecodeAsType, Debug)]
332#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
333struct AccountInfo<Balance> {
334 data: AccountData<Balance>,
335}
336
337#[derive(Clone, Debug, DecodeAsType)]
339#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
340struct AccountData<Balance> {
341 free: Balance,
342 reserved: Balance,
343}
344
345#[derive(Debug, DecodeAsType)]
347#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
348struct BoundedVec<T>(pub ::std::vec::Vec<T>);
349
350#[derive(Debug, DecodeAsType)]
352#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
353struct ContractInfoOf<Hash, Balance> {
354 trie_id: BoundedVec<u8>,
355 code_hash: Hash,
356 storage_items: u32,
357 storage_item_deposit: Balance,
358}
359
360#[derive(Debug, DecodeAsType)]
362#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
363struct DepositAccount<AccountId> {
364 deposit_account: AccountId,
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use ink_env::DefaultEnvironment;
371 use scale::Encode;
372 use scale_info::{
373 IntoPortable,
374 Path,
375 };
376 use subxt::{
377 metadata::{
378 types::Metadata,
379 DecodeWithMetadata,
380 },
381 utils::AccountId32,
382 PolkadotConfig as DefaultConfig,
383 };
384
385 fn get_metadata_type_index(
387 ident: &'static str,
388 module_path: &'static str,
389 metadata: &Metadata,
390 ) -> Result<usize> {
391 let contract_info_path =
392 Path::new(ident, module_path).into_portable(&mut Default::default());
393
394 metadata
395 .types()
396 .types
397 .iter()
398 .enumerate()
399 .find_map(|(i, t)| {
400 if t.ty.path == contract_info_path {
401 Some(i)
402 } else {
403 None
404 }
405 })
406 .ok_or(anyhow!("Type not found"))
407 }
408
409 #[test]
410 fn contract_info_v15_decode_works() {
411 #[subxt::subxt(runtime_metadata_path = "src/test_runtime_api/metadata_v15.scale")]
414 mod api_v15 {}
415
416 use api_v15::runtime_types::{
417 bounded_collections::{
418 bounded_btree_map::BoundedBTreeMap,
419 bounded_vec::BoundedVec,
420 },
421 pallet_contracts::storage::ContractInfo as ContractInfoV15,
422 };
423
424 let metadata_bytes = std::fs::read("src/test_runtime_api/metadata_v15.scale")
425 .expect("the metadata must be present");
426 let metadata =
427 Metadata::decode(&mut &*metadata_bytes).expect("the metadata must decode");
428 let contract_info_type_id = get_metadata_type_index(
429 "ContractInfo",
430 "pallet_contracts::storage",
431 &metadata,
432 )
433 .expect("the contract info type must be present in the metadata");
434
435 let contract_info_v15 = ContractInfoV15 {
436 trie_id: BoundedVec(vec![]),
437 code_hash: Default::default(),
438 storage_bytes: 1,
439 storage_items: 1,
440 storage_byte_deposit: 1,
441 storage_item_deposit: 1,
442 storage_base_deposit: 1,
443 delegate_dependencies: BoundedBTreeMap(vec![]),
444 };
445
446 let contract_info_thunk = DecodedValueThunk::decode_with_metadata(
447 &mut &*contract_info_v15.encode(),
448 contract_info_type_id as u32,
449 &metadata.into(),
450 )
451 .expect("the contract info must be decoded");
452
453 let contract = AccountId32([0u8; 32]);
454 let contract_info_raw =
455 ContractInfoRaw::<DefaultConfig, DefaultEnvironment>::new(
456 contract,
457 contract_info_thunk,
458 )
459 .expect("the conatract info raw must be created");
460 let account_data = AccountData {
461 free: 1,
462 reserved: 10,
463 };
464
465 let contract_info = contract_info_raw.into_contract_info(account_data.clone());
466 assert_eq!(
467 contract_info,
468 ContractInfo {
469 trie_id: contract_info_v15.trie_id.0.into(),
470 code_hash: contract_info_v15.code_hash,
471 storage_items: contract_info_v15.storage_items,
472 storage_items_deposit: contract_info_v15.storage_item_deposit,
473 storage_total_deposit: account_data.reserved,
474 }
475 );
476 }
477}