From a7ef97ecb295934087b809c15396ae543fa2dcbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:38:00 +0000 Subject: [PATCH 1/7] Bump tokio from 1.33.0 to 1.34.0 (#1396) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.33.0 to 1.34.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.33.0...tokio-1.34.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4ea586c6..dd7e655c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2874,9 +2874,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", @@ -4470,9 +4470,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -5296,9 +5296,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -5306,16 +5306,16 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", From e2d9900f506785400c9b5d502e0d3bf1d3a2c961 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Mon, 13 Nov 2023 14:19:07 +0100 Subject: [PATCH 2/7] Prevent submitting calls to messages marked as immutable (#1397) Prevent submitting calls to messages marked as immutable. Signed-off-by: Cyrill Leutwiler Co-authored-by: German --- CHANGELOG.md | 3 +++ crates/extrinsics/src/call.rs | 17 +++++++++++++++++ crates/extrinsics/src/integration_tests.rs | 13 ++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ef098d3..2e0a46426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) +### Fixed +- Do not allow to execute calls on immutable contract messages - [#1397](https://github.com/paritytech/cargo-contract/pull/1397) + ## [4.0.0-alpha] Replaces the yanked `3.1.0` due to issues with supporting *both* Rust versions < `1.70` diff --git a/crates/extrinsics/src/call.rs b/crates/extrinsics/src/call.rs index a670ec1dc..fdcb419f4 100644 --- a/crates/extrinsics/src/call.rs +++ b/crates/extrinsics/src/call.rs @@ -267,6 +267,23 @@ impl CallExec { &self, gas_limit: Option, ) -> Result { + if !self + .transcoder() + .metadata() + .spec() + .messages() + .iter() + .find(|msg| msg.label() == &self.message) + .expect("message exist after calling CallExec::done()") + .mutates() + { + let inner = anyhow!( + "Tried to execute a call on the immutable contract message '{}'. Please do a dry-run instead.", + &self.message + ); + return Err(inner.into()); + } + // use user specified values where provided, otherwise estimate let gas_limit = match gas_limit { Some(gas_limit) => gas_limit, diff --git a/crates/extrinsics/src/integration_tests.rs b/crates/extrinsics/src/integration_tests.rs index abd0fcadf..8bd7206c5 100644 --- a/crates/extrinsics/src/integration_tests.rs +++ b/crates/extrinsics/src/integration_tests.rs @@ -96,14 +96,14 @@ impl ContractsNodeProcess { ); let result = OnlineClient::new().await; if let Ok(client) = result { - break Ok(client) + break Ok(client); } if attempts < MAX_ATTEMPTS { attempts += 1; - continue + continue; } if let Err(err) = result { - break Err(err) + break Err(err); } }; match client { @@ -509,6 +509,13 @@ async fn api_build_upload_instantiate_call() { .to_string(); assert!(value.contains("true"), "{:#?}", value); + // call the contract on the immutable "get" message trying to execute + // this should fail because "get" is immutable + match call.call(None).await { + Err(crate::ErrorVariant::Generic(_)) => {} + _ => panic!("immutable call was not prevented"), + } + // call the contract // flip the value let call = CallCommandBuilder::default() From 631fdeac6c2e9fbaa95f88c66d31f38c6f6284b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:26:13 +0000 Subject: [PATCH 3/7] Bump clap from 4.4.7 to 4.4.8 Bumps [clap](https://github.com/clap-rs/clap) from 4.4.7 to 4.4.8. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/v4.4.7...v4.4.8) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- crates/build/Cargo.toml | 2 +- crates/cargo-contract/Cargo.toml | 2 +- crates/extrinsics/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd7e655c8..822793096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,9 +863,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -873,9 +873,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index 7993cb9cb..fb7e28934 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -19,7 +19,7 @@ anyhow = "1.0.75" blake2 = "0.10.6" cargo_metadata = "0.18.1" colored = "2.0.4" -clap = { version = "4.4.7", features = ["derive", "env"] } +clap = { version = "4.4.8", features = ["derive", "env"] } duct = "0.13.6" heck = "0.4.0" hex = "0.4.3" diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml index 92374fe5c..305493296 100644 --- a/crates/cargo-contract/Cargo.toml +++ b/crates/cargo-contract/Cargo.toml @@ -25,7 +25,7 @@ contract-metadata = { version = "4.0.0-alpha", path = "../metadata" } contract-analyze = { version = "0.1.0", path = "../analyze" } anyhow = "1.0.75" -clap = { version = "4.4.7", features = ["derive", "env"] } +clap = { version = "4.4.8", features = ["derive", "env"] } primitive-types = { version = "0.12.2", default-features = false, features = ["codec", "scale-info", "serde"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index 170b5e901..655ba9101 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -20,7 +20,7 @@ contract-metadata = { version = "4.0.0-alpha", path = "../metadata" } contract-transcode = { version = "4.0.0-alpha", path = "../transcode" } anyhow = "1.0.75" -clap = { version = "4.4.7", features = ["derive", "env"] } +clap = { version = "4.4.8", features = ["derive", "env"] } futures = { version = "0.3.29", default-features = false, features = ["std"] } tracing = "0.1.40" scale = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } From e49b972c29ab6cc730a25fab5d58f3bfd4f2cb5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:30:27 +0000 Subject: [PATCH 4/7] Bump rust_decimal from 1.32.0 to 1.33.0 Bumps [rust_decimal](https://github.com/paupino/rust-decimal) from 1.32.0 to 1.33.0. - [Release notes](https://github.com/paupino/rust-decimal/releases) - [Changelog](https://github.com/paupino/rust-decimal/blob/master/CHANGELOG.md) - [Commits](https://github.com/paupino/rust-decimal/compare/1.32.0...1.33.0) --- updated-dependencies: - dependency-name: rust_decimal dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 97 ++++++++++++++++++------------------ crates/extrinsics/Cargo.toml | 2 +- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 822793096..c2b922f9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,47 +590,26 @@ dependencies = [ [[package]] name = "borsh" -version = "0.10.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +checksum = "bf617fabf5cdbdc92f774bfe5062d870f228b80056d41180797abf48bed4056e" dependencies = [ "borsh-derive", - "hashbrown 0.13.2", + "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.10.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +checksum = "f404657a7ea7b5249e36808dff544bc88a28f26e0ac40009f674b7a009d14be3" dependencies = [ + "once_cell", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", + "syn_derive", ] [[package]] @@ -814,6 +793,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chacha20" version = "0.8.2" @@ -1017,7 +1002,7 @@ dependencies = [ "term_size", "tokio", "tokio-stream", - "toml 0.8.8", + "toml", "tracing", "url", "users", @@ -3318,21 +3303,21 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "0.1.5" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "toml 0.5.11", + "once_cell", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "toml_edit 0.20.7", ] [[package]] @@ -3631,9 +3616,9 @@ checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" [[package]] name = "rust_decimal" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd" +checksum = "076ba1058b036d3ca8bcafb1d54d0b0572e99d7ecd3e4222723e18ca8e9ca9a8" dependencies = [ "arrayvec 0.7.4", "borsh", @@ -5111,6 +5096,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "synstructure" version = "0.13.0" @@ -5358,15 +5355,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.8.8" @@ -5399,6 +5387,17 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + [[package]] name = "toml_edit" version = "0.21.0" diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index 655ba9101..9069db09e 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -28,7 +28,7 @@ colored = "2.0.4" serde = { version = "1.0.192", default-features = false, features = ["derive"] } serde_json = "1.0.108" url = { version = "2.4.1", features = ["serde"] } -rust_decimal = "1.32" +rust_decimal = "1.33" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } sp-core = "22.0.0" sp-runtime = "25.0.0" From 004335117125236957181b6b01e1374195d26035 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:31:11 +0000 Subject: [PATCH 5/7] Bump tracing-subscriber from 0.3.17 to 0.3.18 Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.17 to 0.3.18. - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.17...tracing-subscriber-0.3.18) --- updated-dependencies: - dependency-name: tracing-subscriber dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 23 +++++++++++++++++------ crates/cargo-contract/Cargo.toml | 2 +- crates/extrinsics/Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2b922f9e..1ec07864a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,7 +749,7 @@ dependencies = [ "tempfile", "tokio", "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", "url", "which", ] @@ -1043,7 +1043,7 @@ dependencies = [ "tempfile", "tokio", "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", "url", ] @@ -5482,6 +5482,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -5510,15 +5521,15 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.3", "tracing-serde", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers 0.1.0", "nu-ansi-term", @@ -5529,7 +5540,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", ] [[package]] diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml index 305493296..4d70d9fb6 100644 --- a/crates/cargo-contract/Cargo.toml +++ b/crates/cargo-contract/Cargo.toml @@ -28,7 +28,7 @@ anyhow = "1.0.75" clap = { version = "4.4.8", features = ["derive", "env"] } primitive-types = { version = "0.12.2", default-features = false, features = ["codec", "scale-info", "serde"] } tracing = "0.1.40" -tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } which = "5.0.0" colored = "2.0.4" serde_json = "1.0.108" diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index 9069db09e..68fb1b987 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -45,7 +45,7 @@ assert_cmd = "2.0.12" regex = "1.10.2" predicates = "3.0.4" tempfile = "3.8.1" -tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [features] integration-tests = [] From d7d9acb2b8cde2ddba948934ad08989ba351e5f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:08:06 +0000 Subject: [PATCH 6/7] Bump itertools from 0.11.0 to 0.12.0 Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.11.0 to 0.12.0. - [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-itertools/itertools/compare/v0.11.0...v0.12.0) --- updated-dependencies: - dependency-name: itertools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 11 ++++++++++- crates/transcode/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ec07864a..8018ed2e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1075,7 +1075,7 @@ dependencies = [ "ink", "ink_env", "ink_metadata", - "itertools 0.11.0", + "itertools 0.12.0", "nom", "nom-supreme", "parity-scale-codec", @@ -2496,6 +2496,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" diff --git a/crates/transcode/Cargo.toml b/crates/transcode/Cargo.toml index 03e1d8b4f..f9c2236f5 100644 --- a/crates/transcode/Cargo.toml +++ b/crates/transcode/Cargo.toml @@ -26,7 +26,7 @@ hex = "0.4.3" indexmap = "2.1.0" ink_env = "5.0.0-alpha" ink_metadata = "5.0.0-alpha" -itertools = "0.11.0" +itertools = "0.12.0" tracing = "0.1.40" nom = "7.1.3" nom-supreme = { version = "0.7.0", features = ["error"] } From f0660d0cbcd2e76c8cc4632ef0890482a0a08dc7 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 15 Nov 2023 17:05:04 +0100 Subject: [PATCH 7/7] Add total contract deposit (#1347) * Added functionality to contract info command, which calculates and displays storage total deposit: * Old `Storage Deposit` has been renamed to the `Storage Items Deposit` * The change depends on pallet-contracts, it should work with the versions >=8 --- CHANGELOG.md | 1 + crates/cargo-contract/src/cmd/info.rs | 13 +- crates/cargo-contract/src/cmd/mod.rs | 11 +- crates/extrinsics/src/contract_info.rs | 346 +++++++++++++++++- .../src/runtime_api/metadata_v11.scale | Bin 0 -> 29868 bytes 5 files changed, 340 insertions(+), 31 deletions(-) create mode 100644 crates/extrinsics/src/runtime_api/metadata_v11.scale diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e0a46426..bc4896c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) ### Fixed - Do not allow to execute calls on immutable contract messages - [#1397](https://github.com/paritytech/cargo-contract/pull/1397) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 5d165d878..492b621a3 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -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? @@ -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, } @@ -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, } } diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index aa1078f95..4b7f2d738 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -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. @@ -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!( diff --git a/crates/extrinsics/src/contract_info.rs b/crates/extrinsics/src/contract_info.rs index 405dd6d04..6294e5fd9 100644 --- a/crates/extrinsics/src/contract_info.rs +++ b/crates/extrinsics/src/contract_info.rs @@ -32,45 +32,142 @@ 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, }; +/// 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?; + + let account = client + .storage() + .at(best_block) + .fetch(&storage_query) + .await? + .ok_or_else(|| anyhow::anyhow!("Failed to fetch account data"))?; + + let data = account.as_type::()?.data; + Ok(data) +} + /// Fetch the contract info from the storage using the provided client. 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_value = 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 + ) + })?; + + 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)) +} - 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, - })) +/// Struct representing contract info, supporting deposit on either the main or secondary +/// account. +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_account: AccountId32, + contract_info_value: DecodedValueThunk, + ) -> Result { + 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 + // `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 { + deposit_account, + contract_info, + deposit_on_main_account: false, + }) + } + Err(_) => { + Ok(Self { + deposit_account: contract_account, + contract_info, + deposit_on_main_account: true, + }) + } + } + } + + 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 = if self.deposit_on_main_account { + deposit.reserved + } else { + deposit.free + }; + + 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, } - None => Ok(None), + } + + /// 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, PartialEq, 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 { @@ -95,8 +192,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 } } @@ -161,3 +263,207 @@ 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")] +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")] +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::*; + 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 { + 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 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 {} + + 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_v11 = 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_v11.encode(), + contract_info_type_id as u32, + &metadata.into(), + ) + .expect("the contract info must be decoded"); + + 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!( + 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.free, + } + ); + } + + #[test] + 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")] + 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_v15 = 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_v15.encode(), + contract_info_type_id as u32, + &metadata.into(), + ) + .expect("the contract info must be decoded"); + + 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!( + 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.reserved, + } + ); + } +} 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 zcmdUY4|rr(b>F?aZ`Wgc3|cJ1n8@L|mTRq%r`omF$cpSrD`{C3?XGsEwHNH|%+t)1 zG~UsScyC5pxprb2+(K%IO`*lqB#_|5P~1XmC?qDaThl@dEwn&QV_HZ}O53C*gd~uV zroVH}{qtrdt(~v%CEahoUETBUpWi+A+;h)8_uQ-1xD~zh8#<@dp4}=?YImskPrff* zD^;3FT&N_QYpv$sM5WSfH(E2*8xJaE40L!$d*mm=8xNjUst2X_r~>}INDboOy=p+I zy}hbGKY&^`;@a{`EA5y6OX~*S-Ik<= za${>rZMD{_t;eO-=31Qg96fmGrY$IjmD;7$`*LS0%|xn=NE4%-5D1aWw$cp@q>=#{{L7dUt+n zB~oke^m^QAp(>@rp_xY7ihv+$#Z`2;8n4xxn{l$G!q7tL{-T0sCk;wlx*9HShEcVe z#AzBvt+2Hchv`~esV&vws{K$LScsF=TEjHFL-oO%7f{%KL`|q}BTB=TD_hiqi*X}f zs#R)Hve|_$c%F(!A{-Nr#VJO5HL5jgjpcALsz;4VtP3lyUn^*FF^(IQ ztBJBjRk$WR-fYF{7EKK_8}&`V+63|Hc`wL3Zq6fj3Fp3YNfSOOT(B3BE%2i zR|O-E4^!ndj_M`XzjAS45@Isng0NQu-CrS@0iKmm_hHD7;wQB$c!KhT8@1L7cgr)I zb2|7b*HRp_9h)XJJnohe-TZrSEBm<%hJW0FqVB0it*Br3-4<8I+9S8-4(TZn!nD1% zR;M9H_ga}@EQZH0uc=J9jG;?N1j1@NA--53_+fReR*zb>X5&t9s1e6iYP1O)Lgcem5V#G zN6J|(;`+xeREvB2_Gu4_r@wp&V+eelu(k=;>X^T7s?$7dH2JFq;3w??Jy(yTG!DN;=L6vjGk*T;IFGI2> ziKC>QZCAR)D|fYK#&pKq;EW{jB; z!ZP(eJ|9=wknduL_TedY6zYct{t;;()3Q@SJDNoe7xmGMwK%)JvgR~Z{C_uC!9B!+ zGB2&=FmAM0Wd?wMvg*$5wDJ#`m7j|$9rRBc{-l8#()V>+0;8jxam`WYZX%V(?hA+OEgbNzzQnoau#_$LE#KwMUfhjfp&>A7Df2*u&=a0SYTJ%tFVKHCZh(X=VTH?ywz?sbh>e_(cEZEq|j-w-wH>P z=G}2a?NNiL8?=lx69}N%tH!ehk3!R_i_|by;_35iaihwu=TzU}db4s4`7c&#EjJ8? zL(_OYc(=eTd$>9fDUiIAx@fPzqPV_IJ%HT zjTELFvP5-LU2bTWtie-pR9OK8rTUIHXCs(YWRz92#fPVA=~~pPteBD`y(cjHFjAI1 zv(4tY_8Q9dAC9V`Wr3j}qZ+6&-7&zs)f6NrZLh`&Ryr|ez{K3-ruOo925MYqNA@B$;CU6ODZPbtIW^qDfj}MA+wtUL)jxfH1v-(&@Dc@sCP~_B z#5Hv(4X~G-yc!|X76Up_$9N$7WlG&gwzPT-mb+5V2Za+{k}TmqSi;Af4HCGX`Q~aI zs6Itpeg;{0sXh27;q##G-WmEDsFv=?j$z54iZ%k}twe}& zziH;igPmKp-R{f5p+KECo4JESXGstaQEz(Gw9bmceOpA~J{E-;y>%PZ1AAt5a4HrX zyOyqkP2qF{t7Szd?5VgOoyU)8Xkwf41yRX8v@G*3YG8~kfD+kVvWx76MI5KCd-R-T zSykrpi6yL-(7o$Xy&d1DhYrhL!;t%zqqI~f?NNYLRj-i;myG$tY|2xTSrG1ur)=~U8Xwb#eMT}9Ntl$BO{|VI@;Bh^4a2j zHNQtJ(H=vrOW)nXpKFJxvl*${(nH;oM)g)UNAOH%mtUxV;K7+}p1&YH&+Y}Y*4WlT z-ok=i!6Q4fz;_QFA8f-R5XpujuY0;3{%2FnxDq{GOoN3Deey+IwhX{XZ*)K5gAoKT@r*H(2QG2!Tf@ z7P8a8se%_V|9@1tyXCQ4y={^Fi-LD|_H5f8kxXSi{P}foM^SrWHJ9s__DqM+zrNMl zGI?GCJ!D*;yz=;gx;Q9za>^7y+2Arwn-tiX*tLf1u&&`k46)-3tHz`yOB@?VGLh0+ zMfTRbmzK4Ywu!~7^iEr%L1SV~Lh8D9L=Wgs>NKt|jgFF8zg8=))T-6EQIg$soMHlF zwMyERmfZ!7j&^1l9kn1Y86Bk-2rK=6q?tI+SUMOQ47|a5Sm$RK;b4Xf3JqYPWSc)7 zw6yiOGH_VF!$ix$vQ)u5)>fE11#*gE(O2)?#*|=FB%H(wHUs;Ej0x4Nd$AEB5A29D z9*lj^XV(8Mh6CJEOYHx)e(DRTQt~2TcVCYi#uQbL{t!C%!EHL%XUrD(G1zP|-Qix6 z%euRN>irj}WQYWg+~$r^aDYO*;!>^ph%PMPrx{pmW7b59#OM1yJ=NJd+h04+D%j8L zr710Lw&K(p>yBD$#jB1wm(6Q!vz~6F&gG)AFPv);eyN>usmCwCpgHmu(VpsLJ8s9= z;=$LHFt5RB%)&UK#SMoHabmkQLJk=07COo5qdx?B_~bT7!<2T!a7Yw*hny-kpoiG^ zRcbZYY8A)WzU6uoUU-)-U^|~l4y;89c4Fv=%Q3VH-)^#5T0^HDaiB#rn-6PjkB1kN z(pKtV+Fra1Q#vy#^x#^w$%c=gD0;-~mrBx_oj;$tP+yPE7kuL-xX+Ar3>wVBC$HFY{p>oQ7Msgu1YA#gS9y z+lc>$W3X1{OP|?1W=J7#X5yueZ02kwlWmH(LM7hPF2xBBIWix|;Fu+kQE6+rp+)lX zl;CGujFU`}Jlsf`!CG6iui<;Yx&PAj@v-I9m45?y(Qtxe3QSeXWfD{#su+q8V9@PnSkVx|fQoz->SGsL$HIAI0PqT! z*9G2jq#x1NAkbVp8ShlLsP6O$y)d(74P`TK3@2Bu(vr*h2|eDiQnK0o>1_}h4_+@j z-U*C8KQ`0AN>r;_h%nx-)Q08^7k8pP7{XK=blxoi0W_h@Ar2lT*`XWC#qdPCbzJ+p(cFJ?0+#siO&^OP26oOQy}Se!#qEPRLCOIUt!I55A8mT;V;`Ao6N zAHzld+5s!_RZq;#$hjg%W;B~&N_7-R7RXPHT94ph2=3GXaRGC3DoLlFIP1W^<&AAP zO6ic& zEUm@n`B02>9@Di5=ZYAyCLb}I2|-f3)NZh@p|f@%#vmJ6y)@X58H#y<)7uv)Ya})k znu39haUNM=xrW=_2i#yrtgT@x`v=_P~ z#p2v4w!G(Mi$~l<4%HJp%ghvNW{{D8O-gptjRCD%lLfbcJR(Y1_IZi$^rKF;73 za8e&5yNn;QqH$>TI-SO<5X@*wLDCTg8trT@P#*9l zZPg%Im~9jchDIp73}I)`5)FyBB6p^@3r@iiz)>z&_FSt;r#Qw#8zi30ZJ6v?`#53jAC=lq9Fn%@oQB zQE!4ZWG+d3Fx$P)fM^c^5ze}T{SWDKFdY3Ol?YXYl91m9#rsuaF;7kz&7)o!elN?o zZ2Y2FR;V8kF=ffrw<|6u#8g+unDeXHS77|l#n6K$mXG2j(HH!cIob%xeju5`=<@ow z&15ivuO~G{dYd^Y$`mzWs{o6?4r3KFNF+w8qA1=7T-pe2uVK~H8t1M?4aufNVnPK! zw%Kf(qUaVW1{8rB^lP6jUsNacL)#_sqYL4;c6`_1X-DoBR5g4VF-omBJMQpF^z(TjzD(c6e2qqfnmLU^S zG>qdZW{#*o$Rh~@;M+0aWsnRT3SWkW&qrC1kC_Ix4a*dFuKL(Kq^K}juH@{BB*;mh znNFCRVm}ZEo#bQK*?wV>trq8wH(eWM+W>RrDpE0WislZ!c&@NqM8LMXM%spED@+rQ zfx3w8AA(ZsJ0P~AM%iZcUo`; z>NEPN!8|(Zb09=+UOXCs`kX#$aAtF8)jLhjl0bb)k29R?SD@avt7mA54h2f-CbpBS z1I{K&7q9^%M==}QdjI;)lsP`8@V;k^~^(!-6k5! z)NrRXf)(EHzNgE-m;jw`mGV!=GN47JOH@wBUQWOAEeNxU}FaxU}HA(xnC8D_vUf z{g_J&zCM>0d{^1@je_sTU0U$H%B2Ng=+c7kYL^y#f61i<-!(2R`1)O1@LlWDg6}W8 zwBWnWriTUJfJ+O${Vpx|u6Jp{_iC3Ge1k46_-=4%!B=!?!S`2OTJR0IwBS2n(}x7# zL6;VMH@dXo8+K{IcgUp$-%Tzp_+I1Ef^Wp71>emsE%-)VTJZgZP2VK=##~zP-Qv=M z?^c%d+!3%&`L7JP?YTJTM}wBVa^X~8#b)2|VHM_gL)z1F1#-%*zqd^0XB z`2Lzp3%=L6wBVa{X~B2Qr3K${mlk{{Yfy1E%?s3wBUQaP2Vi|-r&-L?+%w1e0RFE;5+Nmg72@pwBRebwBRedwBUKfn0I-C;5_FE0<`W4f^^jp z4hYhQCkWD}CkWCtPY|SU^aMegc!D5JJwcGRJVB7QJwcGJdx9X{a0Iicoc9Dly6Fjm z^lncOq<_N`1nE7VAV}}^1VQ?fo*+nn$`b_XeV!ml-{c5pdHI{3AV`1O69nm-JwcG( z?+JqRZ+U_s{o9@(NZ;ZKg7mGPAV`1469nnsaRjr_Jm3j}^lhFXNZ;-Wg7jxSL6AP^ z34-(?PY|T<@B~5ncRfLnzS9!~>AM`kEIEJA69nns_XI)uZch-T4|{?jeUB#y(tqFy zg7gtj5Tx(*1VQ?9o*+nn-Vw~=^FB`yr0@3xLHY}xAV`1F69nl8JVB5?>Is7Mmpnm` z{<0?s(#JeOkbck+%rf*LPY|U4&=Umdhdn`%KJE#E^dEVGApOUlAV@#r34-*ao*+p7 zi6;osf9eQkL3+Xy1nI{-L6Cmj69nl$^8`Wq2~QBDpY#Ml`YWCwNPpE61nHBWAV@#u z2xe*eHBS(vzwQZw^eImeq)&T-ApPf_AV~j(CkWDKJVB6t+7krnzw`t_`mZeE;E-tk z&v=3${j4Vl(tqs2G?1ApNhNAV{D01VQ>uM=)#DZ+U_s{coNiNWbL?g7m+8 zf*}1Lo*+p7rzZ%~-}VGS`a7N=NdK272-5%U2xg7?wkHVE-}MAR`W;UYq`&70g7mwd zAV`1T69nlWc!D7PLr)N--}3}P`h7<*Yt$ckf*}24PY|R(@B~5nC!Qck|BojK(mz$| z>Yd#kW8&^uWhX_gwJH7*F-DPOmq(y}TeE9JJ%!t0d9{D%>IS7cj$9dF-wE76mDp7C zT^-^;l_^TyuyZX#Qj5fW401KM^SdF&19M4v)jg-Af9M4Ru%U zG3QxMOWS5JW4BLh*P~muMLmm<1@c`YsL$1T^t7c-+bb76edcbZzK1U=>Ic0CS3lcJ z`67)gvKS9IvnC*3yROvg(6&wXa;$AH->S|TcIhiLF7@IFlJ0kC6vc}W!p|$v1z{;z z;vlA1X+1&%;|gJ1rg!D+Lp~!X)*{3L*@r>MlbyM;r)G9#Z+l*=w_NwU7A|u9Jw_eL zt9ydNDL(F|s~B^^w4T+u+=+96nj_a86h#uKSuXJUUD=zkbKr`wg-L$Y1b#-otA7#h&qq~69pIS}M7d*TAg0MAZX=LufghzKfQG)ZQU%16`rYWur z4Ca#$bhXbgI=~%Yy&c!VZ38$^HzUh-=#@GNS@ENPKx6oCfxz(iAApQl0k}m(W|dTH zuH3I`Bsz1dxT6e)}zwp3JWd>%RB~jt%W~L#63UxS3moPBwA!cx# z;K2iW58+E-S--mXg{vRu!OpZn3KpR(HQX|2ZT9Nf%%?d+RrCNCpZ=$3wVvgRRDOOg zM9a9(Y@=az&e6{yLF?SgT&5L@1HyYatxcu-5mJMzBM1$!tW=vFLKJlfQM6Tv-qfL= z`nRYjC|MMG$e*g2hs*k37jM$dY+8l`EYE%X8V4QSy^Y1Pe^slul3cz~tNT0JXm8bl zZ8U+ucjkx9S(rSW;m#)AgAt`3u-G zz5M9etXatIn-+9UE7y0}yrtD+ZmsG*2y#J;CLcAddn*L!Uah+k(h7{13F>rtqet87 zlzRV*K!hoP2X-iG;7A+?wyf>}0t(TMyXw?^xarePn~kb>@v{{s7`f}adT+;&+`n~5 z?w28X_(k9y0L&>qo=>xAb9$KXHd05Z`L}A_GWY(o9kg!%K&9w>tG1Tq)}tLLd$tHC z9>@dCkL}9Ujm7GY?@R7!yL9jr-+_?;X0o;x5rbo|FMagFWwGeT6)6uj(8f769;4xi zk9TR^QSp{*t?F@r_uBknnuvBWe;BB(GMCl-!?})Q;NCgJi^O2a$OK*@+jOQ!02$zC zIPlcU5d>e?Yj?{M3R`ogfv5aD1>*k^Outagc2+$&O>eiILeywd%>mOe42=-g_O4Nr z@7LP4-HX_jMr#Qf!E@X@Uj`%#hD5552{>=IfOza&GG4^HX#y7{SlvfR;VwV;RSIvC`N z-lxf4@nX8s@>%+r31qT&As^TJ&Q4x9iC4Pwwo}G#)P;oC&-ZE_oC5!OEYgo@T{csQ zEmA*{v><=_!aeVy5U_yv2s*?doaXltI;~Y&BPq7}-yNtzW!w&1dMEgL#WF zGg$)vz6u_~gqbZ}dKep>?rEh5>?^j3q=M%7S_ixu!TYi` zscRX0U&hb)`^{ZD-RyUdo$;GwosXS8we4ePl(FNnv&;i&nw%A^hSI(vSdBF05OiFH z$`AI2@5G}*p9>NAhVW&I0qY8=wb6ml*LXD((TE&0j5s}haFgtp6H8pryHU^A|LpZ3 zwr#0d>}6LH0+>SZod2A;3N9uSOBjXMU%%SKUaQ%tRj$7t0dRal8$}VRO3n~Ok0E9a zdXswzy44V{28wv%%GJc7gNT^Ja)f|FbjQA|8MYfWY^*t2IIJP?5>aQa!nC-bs&HTB z0uzNE`X(uWvtDV}IdGPFNF2*(nn4P0uEhI`homk}QKY0*>xi&3QIv?WOEzk$go7^O zA%*4!jZ8|{fKWv!0m_JyrLfjFBm57czG;o#lAu=?_$@ZR;vtLTs9FgRAn2WgdXYJc zyI2noB3{y7e&F~>+o$3W!W$8ux{8&E-n~VTC*oSA5()ye*}CG?y=pDu@hQjnMPRl4 zv!yX`2&|>MU=*WG4?7}?lCIiU!+Lag4Gm)ZPLS{cQGqKh)bM)kB9&2+YC>#i=Yac& z;u;4;%B_oRuZim>#u+!LDB76n^`XBT=v_oX*9ijo5kG6#B0^{4pxN0Iig&A{XKs|D zMci0CsX0c{+zo)_5#*Et`ndu`4%4%jDZ-YyL4InJvKTgk%U0CwO&y9vo`G#{gvCZO zbI5Jxja|q#LN`|-))fpM>PsLa*yGUsH89@CaMCpqLN%0yrTRkmbZ55Ne0 zjOQM$61XIgHt~Wni3mUAi>Gn%O0j#;6jvC+h1*Y@x#9SUh3V07e&Wb9ZUWJiDMQN> zD`%?wSSMcH#cQ3AE1IOm;i>GS5YT3O@{hnW{7g8#UgPUYu47;nLc^nt2#l!UvJb?Q zM3=>ubqz*lax0zs8HI5Ct#A4pk0@p1ZcR+FzaHa872fG|LS!C&#z>_RLb9n*3Y5lB z@}(XQ_;pt)sWlfjYo`*5ma#ReRqJuN*!c>^q{x_7kA+70s@yRRRlXM05}Ke+_gZl@ z;oJ}fQn=?>s7do7tlF{)>q!FdE9+6qp2zHj1}M&Kr@Jczl+bbQc>7>w^V zBuo*=cps8PfF+zC>B9v8nP2w3S<6qk5k`Z7(NCe`vt2-+;o;!8XzDdhL)Q+jvQjiR zsbRbfj8bG?VivQer<~ZDQ|I~a0lLkXFGwQv19~WQS9+h^zX$G0Uj5<7Q1{_SY%Ap- zSOg*cJWl_p@!z{#sk>IMP=$Bw{^s7*>u){WbJ4xmFFkyvD!k|Hr7Hi*mtLaszw+^3 zRrvVft5x9_a8t7IYZWB#ew=8(ez_`qr+}Zn^PV@Tk)Qn4eX3CFQ8)ecFRCXl+p9kG z$=!eavGcdS<)_p@^1HvI2EOt$)gM1_-S;n1H|1Zd`eyd2_x#4auXZ1DtI9ZbNiHjZ$9=a^JYxv$*JsRx0Bslo?yRM6_Te|Dg zb^Yo;y6!8LYxlqXj$0o(aO&cH>K)g7`1>;_cg3H-YG&Zr%habXS3iCULOUPYr>=kf z0To_+NFDqfLO1WfT)jMxkj>iV>LtIl>(WE|H;+8O>#FSNUEP-z*k--seZe zv;n%oj74G=Z#H341*-QcUN}~x^Yl6jZWT3_ac2aBmsx;#Re{!_aZQT~W&pIRVWwQ| zqZO<|k3Z7|AL!dQUHil38seY3Y-X2h(ggHQ0hnpmYaWkphkOmi-29+bXOJ$uZN$h& zQifu8Sez)!mq|$qVaHa^oz=8dv{udzT;tb2awirikp*Wcm2>-xA)P~bKxIISm&s|2 zNAhvlxxdVknBx6cWWp&0ewjmY92}b$BN5vi=G%Q3YMAlFgbqA(lf`n-vP)`|@^~bC z6L%4D01_?5_?Mp#nxSWumS8|F1iByd?HFCDbGn}T$JN^F#i?C0z>)GbceG6{XUqPs zP7%LK0F&PAX23VB#&mD2GrOD2JYo=3;cVgu+Ig~x)hH(TI9^W@U#2bPj%eqC$8bN-eudr;tDlFI z*ecb?fo?IlT9ikbfS29a!R-JHbdO7#)Vrl$*nSQSFPw-W8TjC-~5n=7Lnfp zp{~`z105W&Uqqu>2FzM=DQelPGlAt|e!uSd!1k4pS!?=ZSj!vd8L@+;qh<4jOlY$0 z-F4PMnL>QIF<}RISjLVXlc=r3)0m%kl;`<1LI=*&l$OuZjKej3?BH?#3wtK~>*{sA zw|DfqhkI&Eg?Pp}-`T~TYr|BeB?7aYtVmJ~=KTXwO<~#9?KxR4;>8c1XXRsNV4$67 zgcI5lus#w7K$)2gqBI~ac8G=!)1zF6+t@zDNzWMkF7-_xr4aPTHeldNJ%MnIkiGI5 zv31{zINo)mi5|EU5aK0lQMw#jgV91)vFJpVuT!$Jb5)Sq<6?A2w$_-HffO-^u7g@C zj$m}i({E;nxn$vT6gk|844E;lOEP4Ns)Z*ez% z!5`XPQK!@bW!wJmcOT`-i#^V4H?RX!7wn(Sy0sk`1v@VP)U>GAs^Mm##JO z`xi*rP<6EJx4MM0-Xns6uCoq+1!Bco>Ly>-@y@&9tRz;kIe_Bu+ihb`Gnd?6lzw%U z*b5onq#tlzEmCjtYoD+->w-ekO#enZWKL1|r`LW)?>2IaYC*HI-Z)&}~Jm zx&+%+3YQQyL<+MwIFHfA1{sEDnp?1LSll~hYKhFrpeMx!siyn;uE=N%a^3I~A|@+a zp3==G|IP#GZe0I{+eaQx5?EAI@nYd3)on!(V@F#Ph&nL}+^5b_1|pIf2gAS0xA=|r z9wSY~fgLal$e8;GcaiG%m!?rGJ@||H7M9zsrI9ikq0u1!RMv=rEnI=*@^AMF=O-s- zr{QLxEeRY5ZFn0hVph+>g9c(JbNWoq{!N{KcgSZHGPR|{rhJx3MfF@q>+lpaBTd#M z+Q_yVzM2{jd7lA=Z-z^_ZA}*%$T8ztV-G&b(6HGI$*X5UpGoHO!Bf!te$v`KlQ_b$ zEfe4wPaW=jGjr(;0Zd$YhOhv16e}()ctvJH%z&Y1fb$~$2;mj^_W9P8IuC<5|0b=b z?NHG|Y|5Q2 zcl;uR9t%!>AHq%y?0ALyi#q?UP|l7G26+8RHPgPpHzh`rWt$j*N6^xFPr~5}!pFOdH4_!SQcQcP}8T0YUrEi_c+jg?*pcDfMH;d) z=8GG<#JrP$HA3?g9x84{$)vM<=qqp$9phbjsQ8o2tp?4Okm| RQMN=RbU!UVIQ%uO{x5i`?R@|M literal 0 HcmV?d00001