From 6377e2d11f78437b08ac7a6eb36268d90cc54302 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Thu, 25 Jan 2024 19:51:42 +0100 Subject: [PATCH 01/26] Reexport `ContractCallBuilder` from `ink_e2e` --- crates/e2e/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index ad8bc898950..abedb568297 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -53,6 +53,7 @@ pub use contract_results::{ InstantiationResult, UploadResult, }; +pub use ink::codegen::ContractCallBuilder; pub use ink_e2e_macro::test; pub use node_proc::{ TestNodeProcess, From 05fc1dd6b87e9b41451f6704127bc17a03a41415 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 09:22:56 +0100 Subject: [PATCH 02/26] Add `create_call_builder(AccountId)` --- CHANGELOG.md | 1 + crates/e2e/src/lib.rs | 20 ++++++++++++ integration-tests/flipper/Cargo.toml | 1 + integration-tests/flipper/lib.rs | 46 ++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 348b70f7f46..231ca36d2b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Custom signature topic in Events - [#2031](https://github.com/paritytech/ink/pull/2031) - Linter: `non_fallible_api` lint - [#2004](https://github.com/paritytech/ink/pull/2004) - Linter: Publish the linting crates on crates.io - [#2060](https://github.com/paritytech/ink/pull/2060) +- [E2E] Added `create_call_builder` for testing existing contracts - [#2075](https://github.com/paritytech/ink/pull/2075) ### Fixed - Fix the `StorageVec` type by excluding the `len_cached` field from its type info - [#2052](https://github.com/paritytech/ink/pull/2052) diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index abedb568297..4183b110281 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -83,6 +83,11 @@ pub use { drink_client::Client as DrinkClient, }; +use ink_env::{ + call::FromAccountId, + ContractEnv, + Environment, +}; use pallet_contracts_primitives::{ ContractExecResult, ContractInstantiateResult, @@ -129,6 +134,21 @@ pub fn account_id(account: AccountKeyring) -> ink_primitives::AccountId { .expect("account keyring has a valid account id") } +/// Creates a call builder builder for `Contract`, based on an account id. +pub fn create_call_builder( + acc_id: <::Env as Environment>::AccountId, +) -> ::Type + where + ::Env: Environment, + Contract: ContractCallBuilder, + Contract: ContractEnv, + Contract::Type: FromAccountId<::Env>, +{ + <::Type as FromAccountId< + ::Env, + >>::from_account_id(acc_id) +} + /// Builds a contract and imports its scaffolded structure as a module. #[macro_export] macro_rules! build { diff --git a/integration-tests/flipper/Cargo.toml b/integration-tests/flipper/Cargo.toml index 3227a40778b..b08070cb703 100644 --- a/integration-tests/flipper/Cargo.toml +++ b/integration-tests/flipper/Cargo.toml @@ -10,6 +10,7 @@ ink = { path = "../../crates/ink", default-features = false } [dev-dependencies] ink_e2e = { path = "../../crates/e2e" } +hex-literal = { version = "0.4.1" } [lib] path = "lib.rs" diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index ccc6aa2cff7..5a3cff8b277 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -110,5 +110,51 @@ pub mod flipper { Ok(()) } + + /// This test illustrates how to test an existing on-chain contract. + /// + /// You can utilize this to e.g. create a snapshot of a production chain + /// and run the E2E tests against a deployed contract there. + /// This process is explained [here](https://use.ink/5.x/basics/contract-testing/chain-snapshot). + /// It requires a node to be run in the background, before executing the test. + /// + /// The test is run like this: + /// + /// ``` + /// # The env variable needs to be set, otherwise `ink_e2e` will spawn a new + /// # node process for each test. + /// $ export CONTRACTS_NODE_URL=ws://127.0.0.1:9944 + /// + /// $ cargo test --features e2e-tests e2e_test_deployed_contract + /// ``` + /// + #[ink_e2e::test] + #[ignore] + async fn e2e_test_deployed_contract( + mut client: Client, + ) -> E2EResult<()> { + // given + // You can take a SS58 address and convert it to hex using the `subkey` tool: + // + // ``` + // subkey inspect 5D4zzvxGZq4wx3SYuaDomSMSAS1h1Knm35gz62UAk4Tqscey + // ... + // Public key (hex): 0x2c75f0aa09dbfbfd49e6286a0f2edd3b4913f04a58b13391c79e96782f5713e3 + // ``` + let acc_id = hex_literal::hex!( + "2c75f0aa09dbfbfd49e6286a0f2edd3b4913f04a58b13391c79e96782f5713e3" + ); + let acc_id = AccountId::from(acc_id); + + // when + // Invoke `Flipper::get()` from Bob + let call_builder = ink_e2e::create_call_builder::(acc_id); + let get = call_builder.get(); + let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; + + // then + assert!(matches!(get_res.return_value(), true)); + Ok(()) + } } } From ed61d63a4a8dc0e88ef82d46f378a05185a823b2 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 10:50:10 +0100 Subject: [PATCH 03/26] Revert "Reexport `ContractCallBuilder` from `ink_e2e`" This reverts commit 6377e2d11f78437b08ac7a6eb36268d90cc54302. --- crates/e2e/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index 4183b110281..36bd20b1c1f 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -53,7 +53,6 @@ pub use contract_results::{ InstantiationResult, UploadResult, }; -pub use ink::codegen::ContractCallBuilder; pub use ink_e2e_macro::test; pub use node_proc::{ TestNodeProcess, From a8730f5805d5ec243a9470de95a22a4975445367 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 10:54:40 +0100 Subject: [PATCH 04/26] Apply `cargo fmt` --- crates/e2e/src/lib.rs | 10 +++++----- integration-tests/flipper/lib.rs | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index 36bd20b1c1f..caf8174033c 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -137,11 +137,11 @@ pub fn account_id(account: AccountKeyring) -> ink_primitives::AccountId { pub fn create_call_builder( acc_id: <::Env as Environment>::AccountId, ) -> ::Type - where - ::Env: Environment, - Contract: ContractCallBuilder, - Contract: ContractEnv, - Contract::Type: FromAccountId<::Env>, +where + ::Env: Environment, + Contract: ContractCallBuilder, + Contract: ContractEnv, + Contract::Type: FromAccountId<::Env>, { <::Type as FromAccountId< ::Env, diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index 5a3cff8b277..66d56f02fda 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -127,7 +127,6 @@ pub mod flipper { /// /// $ cargo test --features e2e-tests e2e_test_deployed_contract /// ``` - /// #[ink_e2e::test] #[ignore] async fn e2e_test_deployed_contract( From 16585d56945017af15d1d83ee091eab31a31def7 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 11:29:13 +0100 Subject: [PATCH 05/26] Add CI test --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e016d71f6d..09b56b316c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -487,6 +487,11 @@ jobs: # run all tests with --all-features, which will run the e2e-tests feature if present scripts/for_all_contracts_exec.sh --path integration-tests --ignore static-buffer -- cargo test \ --verbose --all-features --manifest-path {} + # run flipper E2E test with on-chain contract + cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml + cargo contract instantiate --suri //Alice --args true -x -y --manifest-path=integration-tests/flipper/Cargo.toml + FLIPPER=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) + cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored # run the static buffer test with a custom buffer size cargo clean --manifest-path integration-tests/static-buffer/Cargo.toml INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path integration-tests/static-buffer/Cargo.toml --all-features From db87053fbd4440411334af2a23090b231a97bf57 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 11:29:21 +0100 Subject: [PATCH 06/26] Improve code --- crates/e2e/src/lib.rs | 1 + integration-tests/flipper/Cargo.toml | 2 +- integration-tests/flipper/lib.rs | 32 +++++++++++++++------------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index caf8174033c..a44e094a1f1 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -82,6 +82,7 @@ pub use { drink_client::Client as DrinkClient, }; +use ink::codegen::ContractCallBuilder; use ink_env::{ call::FromAccountId, ContractEnv, diff --git a/integration-tests/flipper/Cargo.toml b/integration-tests/flipper/Cargo.toml index b08070cb703..9547f91c7ef 100644 --- a/integration-tests/flipper/Cargo.toml +++ b/integration-tests/flipper/Cargo.toml @@ -10,7 +10,7 @@ ink = { path = "../../crates/ink", default-features = false } [dev-dependencies] ink_e2e = { path = "../../crates/e2e" } -hex-literal = { version = "0.4.1" } +hex = { version = "0.4.3" } [lib] path = "lib.rs" diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index 66d56f02fda..6a96811f824 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -116,37 +116,39 @@ pub mod flipper { /// You can utilize this to e.g. create a snapshot of a production chain /// and run the E2E tests against a deployed contract there. /// This process is explained [here](https://use.ink/5.x/basics/contract-testing/chain-snapshot). - /// It requires a node to be run in the background, before executing the test. /// - /// The test is run like this: + /// Before executing the test: + /// * Make sure you have a node running in the background, + /// * Supply the env `CONTRACT_HEX` that points to a deployed flipper. You can + /// take the SS58 address which `cargo contract instantiate` gives you and + /// convert it to hex using `subkey inspect `. + /// + /// The test is then run like this: /// /// ``` /// # The env variable needs to be set, otherwise `ink_e2e` will spawn a new /// # node process for each test. /// $ export CONTRACTS_NODE_URL=ws://127.0.0.1:9944 /// - /// $ cargo test --features e2e-tests e2e_test_deployed_contract + /// $ export CONTRACT_HEX=0x2c75f0aa09dbfbfd49e6286a0f2edd3b4913f04a58b13391c79e96782f5713e3 + /// $ cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored /// ``` + /// + /// # Developer Note + /// + /// The test is marked as ignored, as it has the above pre-conditions to succeed. #[ink_e2e::test] #[ignore] async fn e2e_test_deployed_contract( mut client: Client, ) -> E2EResult<()> { // given - // You can take a SS58 address and convert it to hex using the `subkey` tool: - // - // ``` - // subkey inspect 5D4zzvxGZq4wx3SYuaDomSMSAS1h1Knm35gz62UAk4Tqscey - // ... - // Public key (hex): 0x2c75f0aa09dbfbfd49e6286a0f2edd3b4913f04a58b13391c79e96782f5713e3 - // ``` - let acc_id = hex_literal::hex!( - "2c75f0aa09dbfbfd49e6286a0f2edd3b4913f04a58b13391c79e96782f5713e3" - ); - let acc_id = AccountId::from(acc_id); + let addr = std::env::var("HEX").unwrap().replace("0x", ""); + let acc_id = hex::decode(addr).unwrap(); + let acc_id = AccountId::try_from(&acc_id[..]).unwrap(); // when - // Invoke `Flipper::get()` from Bob + // Invoke `Flipper::get()` from Bob's account let call_builder = ink_e2e::create_call_builder::(acc_id); let get = call_builder.get(); let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; From 77a7d27cba02f8b03f240745e87879a251f5f4a6 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 11:36:07 +0100 Subject: [PATCH 07/26] Make spellchecker happy --- .config/cargo_spellcheck.dic | 3 ++- integration-tests/flipper/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index 416855e72bc..5d6a9cfb7be 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -68,6 +68,7 @@ scalability scalable scale_derive sr25519 +SS58 stdin stdout subber @@ -127,4 +128,4 @@ GB BufferTooSmall KeyNotFound ink_env -^ \ No newline at end of file +^ diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index 6a96811f824..8e1cae9c9e6 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -119,9 +119,9 @@ pub mod flipper { /// /// Before executing the test: /// * Make sure you have a node running in the background, - /// * Supply the env `CONTRACT_HEX` that points to a deployed flipper. You can - /// take the SS58 address which `cargo contract instantiate` gives you and - /// convert it to hex using `subkey inspect `. + /// * Supply the environment variable `CONTRACT_HEX` that points to a deployed + /// flipper contract. You can take the SS58 address which `cargo contract instantiate` + /// gives you and convert it to hex using `subkey inspect `. /// /// The test is then run like this: /// From 9b304d37ab41873e3643213081af43eb0364c9cc Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:00:00 +0100 Subject: [PATCH 08/26] Fix CI --- .github/workflows/ci.yml | 4 ++-- integration-tests/flipper/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09b56b316c9..2c8eefbe2e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,8 +490,8 @@ jobs: # run flipper E2E test with on-chain contract cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml cargo contract instantiate --suri //Alice --args true -x -y --manifest-path=integration-tests/flipper/Cargo.toml - FLIPPER=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) - cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored + export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) + CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored # run the static buffer test with a custom buffer size cargo clean --manifest-path integration-tests/static-buffer/Cargo.toml INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path integration-tests/static-buffer/Cargo.toml --all-features diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index 8e1cae9c9e6..b2ff2463a0d 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -143,7 +143,7 @@ pub mod flipper { mut client: Client, ) -> E2EResult<()> { // given - let addr = std::env::var("HEX").unwrap().replace("0x", ""); + let addr = std::env::var("CONTRACT_HEX").unwrap().replace("0x", ""); let acc_id = hex::decode(addr).unwrap(); let acc_id = AccountId::try_from(&acc_id[..]).unwrap(); From 9900cc92777095d264b5fe5f78b0fa3ebe8640f8 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:01:55 +0100 Subject: [PATCH 09/26] Revert me: Run CI with isolated examples --- .../basic-contract-caller/.gitignore | 9 - .../basic-contract-caller/Cargo.toml | 32 - .../basic-contract-caller/lib.rs | 41 - .../other-contract/.gitignore | 9 - .../other-contract/Cargo.toml | 23 - .../other-contract/lib.rs | 33 - .../call-builder-return-value/Cargo.toml | 27 - .../call-builder-return-value/lib.rs | 298 ----- integration-tests/call-runtime/.gitignore | 9 - integration-tests/call-runtime/Cargo.toml | 34 - integration-tests/call-runtime/README.md | 29 - integration-tests/call-runtime/lib.rs | 284 ----- .../combined-extension/.gitignore | 9 - .../combined-extension/Cargo.toml | 26 - .../combined-extension/README.md | 19 - integration-tests/combined-extension/lib.rs | 189 --- .../conditional-compilation/.gitignore | 9 - .../conditional-compilation/Cargo.toml | 25 - .../conditional-compilation/lib.rs | 161 --- integration-tests/contract-storage/.gitignore | 9 - integration-tests/contract-storage/Cargo.toml | 23 - .../contract-storage/e2e_tests.rs | 122 -- integration-tests/contract-storage/lib.rs | 76 -- .../contract-terminate/.gitignore | 9 - .../contract-terminate/Cargo.toml | 23 - integration-tests/contract-terminate/lib.rs | 97 -- .../contract-transfer/.gitignore | 9 - .../contract-transfer/Cargo.toml | 23 - integration-tests/contract-transfer/lib.rs | 264 ---- integration-tests/custom-allocator/.gitignore | 9 - integration-tests/custom-allocator/Cargo.toml | 28 - integration-tests/custom-allocator/lib.rs | 170 --- .../custom-environment/.gitignore | 9 - .../custom-environment/Cargo.toml | 26 - .../custom-environment/README.md | 28 - integration-tests/custom-environment/lib.rs | 146 --- integration-tests/dns/.gitignore | 9 - integration-tests/dns/Cargo.toml | 19 - integration-tests/dns/lib.rs | 262 ---- integration-tests/e2e-call-runtime/.gitignore | 9 - integration-tests/e2e-call-runtime/Cargo.toml | 23 - integration-tests/e2e-call-runtime/lib.rs | 90 -- .../e2e-runtime-only-backend/.gitignore | 9 - .../e2e-runtime-only-backend/Cargo.toml | 23 - .../e2e-runtime-only-backend/lib.rs | 159 --- integration-tests/erc1155/.gitignore | 9 - integration-tests/erc1155/Cargo.toml | 19 - integration-tests/erc1155/lib.rs | 805 ------------ integration-tests/erc20/.gitignore | 9 - integration-tests/erc20/Cargo.toml | 23 - integration-tests/erc20/lib.rs | 643 ---------- integration-tests/erc721/.gitignore | 9 - integration-tests/erc721/Cargo.toml | 19 - integration-tests/erc721/lib.rs | 637 ---------- integration-tests/events/.gitignore | 9 - integration-tests/events/Cargo.toml | 35 - .../events/event-def-unused/Cargo.toml | 13 - .../events/event-def-unused/src/lib.rs | 15 - integration-tests/events/event-def/Cargo.toml | 13 - integration-tests/events/event-def/src/lib.rs | 14 - .../events/event-def2/Cargo.toml | 13 - .../events/event-def2/src/lib.rs | 9 - integration-tests/events/lib.rs | 415 ------ integration-tests/incrementer/.gitignore | 9 - integration-tests/incrementer/Cargo.toml | 19 - integration-tests/incrementer/lib.rs | 57 - .../lang-err-integration-tests/.gitignore | 9 - .../call-builder-delegate/Cargo.toml | 27 - .../call-builder-delegate/lib.rs | 185 --- .../call-builder/Cargo.toml | 29 - .../call-builder/lib.rs | 587 --------- .../constructors-return-value/Cargo.toml | 23 - .../constructors-return-value/lib.rs | 250 ---- .../contract-ref/Cargo.toml | 26 - .../contract-ref/lib.rs | 188 --- .../integration-flipper/Cargo.toml | 23 - .../integration-flipper/lib.rs | 152 --- .../lazyvec-integration-test/.gitignore | 9 - .../lazyvec-integration-test/Cargo.toml | 23 - .../lazyvec-integration-test/lib.rs | 156 --- .../.cargo/config.toml | 3 - .../mapping-integration-tests/.gitignore | 9 - .../mapping-integration-tests/Cargo.toml | 23 - .../mapping-integration-tests/lib.rs | 418 ------- integration-tests/mother/.gitignore | 12 - integration-tests/mother/Cargo.toml | 23 - integration-tests/mother/lib.rs | 251 ---- .../multi-contract-caller/.gitignore | 9 - .../.images/code-hashes.png | Bin 95399 -> 0 bytes .../multi-contract-caller/Cargo.toml | 38 - .../multi-contract-caller/README.md | 44 - .../accumulator/Cargo.toml | 17 - .../multi-contract-caller/accumulator/lib.rs | 35 - .../multi-contract-caller/adder/Cargo.toml | 22 - .../multi-contract-caller/adder/lib.rs | 32 - .../multi-contract-caller/build-all.sh | 8 - .../multi-contract-caller/lib.rs | 212 ---- .../multi-contract-caller/subber/Cargo.toml | 22 - .../multi-contract-caller/subber/lib.rs | 32 - integration-tests/multisig/.gitignore | 9 - integration-tests/multisig/Cargo.toml | 19 - integration-tests/multisig/lib.rs | 1112 ----------------- integration-tests/payment-channel/.gitignore | 9 - integration-tests/payment-channel/Cargo.toml | 25 - integration-tests/payment-channel/lib.rs | 590 --------- integration-tests/psp22-extension/.gitignore | 9 - integration-tests/psp22-extension/Cargo.toml | 19 - integration-tests/psp22-extension/README.md | 47 - integration-tests/psp22-extension/lib.rs | 265 ---- .../runtime/psp22-extension-example.rs | 446 ------- integration-tests/rand-extension/.gitignore | 9 - integration-tests/rand-extension/Cargo.toml | 19 - integration-tests/rand-extension/README.md | 35 - integration-tests/rand-extension/lib.rs | 160 --- .../runtime/chain-extension-example.rs | 58 - .../trait-dyn-cross-contract-calls/Cargo.toml | 33 - .../contracts/incrementer/.gitignore | 9 - .../contracts/incrementer/Cargo.toml | 22 - .../contracts/incrementer/lib.rs | 56 - .../trait-dyn-cross-contract-calls/lib.rs | 135 -- .../traits/.gitignore | 9 - .../traits/Cargo.toml | 19 - .../traits/lib.rs | 16 - integration-tests/trait-erc20/.gitignore | 9 - integration-tests/trait-erc20/Cargo.toml | 19 - integration-tests/trait-erc20/lib.rs | 549 -------- integration-tests/trait-flipper/.gitignore | 9 - integration-tests/trait-flipper/Cargo.toml | 19 - integration-tests/trait-flipper/lib.rs | 66 - .../trait-incrementer/.gitignore | 9 - .../trait-incrementer/Cargo.toml | 22 - integration-tests/trait-incrementer/lib.rs | 70 -- .../trait-incrementer/traits/.gitignore | 9 - .../trait-incrementer/traits/Cargo.toml | 21 - .../trait-incrementer/traits/lib.rs | 37 - .../upgradeable-contracts/README.md | 36 - .../delegator/.gitignore | 9 - .../delegator/Cargo.toml | 24 - .../delegator/delegatee/.gitignore | 9 - .../delegator/delegatee/Cargo.toml | 20 - .../delegator/delegatee/lib.rs | 43 - .../upgradeable-contracts/delegator/lib.rs | 208 --- .../set-code-hash/Cargo.toml | 23 - .../set-code-hash/lib.rs | 138 -- .../updated-incrementer/Cargo.toml | 19 - .../set-code-hash/updated-incrementer/lib.rs | 69 - .../wildcard-selector/.gitignore | 9 - .../wildcard-selector/Cargo.toml | 23 - integration-tests/wildcard-selector/lib.rs | 163 --- 149 files changed, 13393 deletions(-) delete mode 100755 integration-tests/basic-contract-caller/.gitignore delete mode 100755 integration-tests/basic-contract-caller/Cargo.toml delete mode 100755 integration-tests/basic-contract-caller/lib.rs delete mode 100755 integration-tests/basic-contract-caller/other-contract/.gitignore delete mode 100755 integration-tests/basic-contract-caller/other-contract/Cargo.toml delete mode 100755 integration-tests/basic-contract-caller/other-contract/lib.rs delete mode 100755 integration-tests/call-builder-return-value/Cargo.toml delete mode 100755 integration-tests/call-builder-return-value/lib.rs delete mode 100644 integration-tests/call-runtime/.gitignore delete mode 100644 integration-tests/call-runtime/Cargo.toml delete mode 100644 integration-tests/call-runtime/README.md delete mode 100644 integration-tests/call-runtime/lib.rs delete mode 100755 integration-tests/combined-extension/.gitignore delete mode 100755 integration-tests/combined-extension/Cargo.toml delete mode 100644 integration-tests/combined-extension/README.md delete mode 100755 integration-tests/combined-extension/lib.rs delete mode 100755 integration-tests/conditional-compilation/.gitignore delete mode 100755 integration-tests/conditional-compilation/Cargo.toml delete mode 100755 integration-tests/conditional-compilation/lib.rs delete mode 100755 integration-tests/contract-storage/.gitignore delete mode 100755 integration-tests/contract-storage/Cargo.toml delete mode 100644 integration-tests/contract-storage/e2e_tests.rs delete mode 100755 integration-tests/contract-storage/lib.rs delete mode 100644 integration-tests/contract-terminate/.gitignore delete mode 100644 integration-tests/contract-terminate/Cargo.toml delete mode 100644 integration-tests/contract-terminate/lib.rs delete mode 100644 integration-tests/contract-transfer/.gitignore delete mode 100644 integration-tests/contract-transfer/Cargo.toml delete mode 100644 integration-tests/contract-transfer/lib.rs delete mode 100755 integration-tests/custom-allocator/.gitignore delete mode 100755 integration-tests/custom-allocator/Cargo.toml delete mode 100755 integration-tests/custom-allocator/lib.rs delete mode 100644 integration-tests/custom-environment/.gitignore delete mode 100644 integration-tests/custom-environment/Cargo.toml delete mode 100644 integration-tests/custom-environment/README.md delete mode 100644 integration-tests/custom-environment/lib.rs delete mode 100644 integration-tests/dns/.gitignore delete mode 100644 integration-tests/dns/Cargo.toml delete mode 100644 integration-tests/dns/lib.rs delete mode 100644 integration-tests/e2e-call-runtime/.gitignore delete mode 100644 integration-tests/e2e-call-runtime/Cargo.toml delete mode 100644 integration-tests/e2e-call-runtime/lib.rs delete mode 100644 integration-tests/e2e-runtime-only-backend/.gitignore delete mode 100644 integration-tests/e2e-runtime-only-backend/Cargo.toml delete mode 100644 integration-tests/e2e-runtime-only-backend/lib.rs delete mode 100644 integration-tests/erc1155/.gitignore delete mode 100644 integration-tests/erc1155/Cargo.toml delete mode 100644 integration-tests/erc1155/lib.rs delete mode 100644 integration-tests/erc20/.gitignore delete mode 100644 integration-tests/erc20/Cargo.toml delete mode 100644 integration-tests/erc20/lib.rs delete mode 100644 integration-tests/erc721/.gitignore delete mode 100644 integration-tests/erc721/Cargo.toml delete mode 100644 integration-tests/erc721/lib.rs delete mode 100644 integration-tests/events/.gitignore delete mode 100644 integration-tests/events/Cargo.toml delete mode 100644 integration-tests/events/event-def-unused/Cargo.toml delete mode 100644 integration-tests/events/event-def-unused/src/lib.rs delete mode 100644 integration-tests/events/event-def/Cargo.toml delete mode 100644 integration-tests/events/event-def/src/lib.rs delete mode 100644 integration-tests/events/event-def2/Cargo.toml delete mode 100644 integration-tests/events/event-def2/src/lib.rs delete mode 100644 integration-tests/events/lib.rs delete mode 100644 integration-tests/incrementer/.gitignore delete mode 100644 integration-tests/incrementer/Cargo.toml delete mode 100644 integration-tests/incrementer/lib.rs delete mode 100755 integration-tests/lang-err-integration-tests/.gitignore delete mode 100755 integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml delete mode 100755 integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs delete mode 100755 integration-tests/lang-err-integration-tests/call-builder/Cargo.toml delete mode 100755 integration-tests/lang-err-integration-tests/call-builder/lib.rs delete mode 100644 integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml delete mode 100644 integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs delete mode 100755 integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml delete mode 100755 integration-tests/lang-err-integration-tests/contract-ref/lib.rs delete mode 100644 integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml delete mode 100644 integration-tests/lang-err-integration-tests/integration-flipper/lib.rs delete mode 100755 integration-tests/lazyvec-integration-test/.gitignore delete mode 100755 integration-tests/lazyvec-integration-test/Cargo.toml delete mode 100755 integration-tests/lazyvec-integration-test/lib.rs delete mode 100644 integration-tests/mapping-integration-tests/.cargo/config.toml delete mode 100755 integration-tests/mapping-integration-tests/.gitignore delete mode 100755 integration-tests/mapping-integration-tests/Cargo.toml delete mode 100755 integration-tests/mapping-integration-tests/lib.rs delete mode 100755 integration-tests/mother/.gitignore delete mode 100755 integration-tests/mother/Cargo.toml delete mode 100755 integration-tests/mother/lib.rs delete mode 100644 integration-tests/multi-contract-caller/.gitignore delete mode 100644 integration-tests/multi-contract-caller/.images/code-hashes.png delete mode 100644 integration-tests/multi-contract-caller/Cargo.toml delete mode 100644 integration-tests/multi-contract-caller/README.md delete mode 100644 integration-tests/multi-contract-caller/accumulator/Cargo.toml delete mode 100644 integration-tests/multi-contract-caller/accumulator/lib.rs delete mode 100644 integration-tests/multi-contract-caller/adder/Cargo.toml delete mode 100644 integration-tests/multi-contract-caller/adder/lib.rs delete mode 100755 integration-tests/multi-contract-caller/build-all.sh delete mode 100644 integration-tests/multi-contract-caller/lib.rs delete mode 100644 integration-tests/multi-contract-caller/subber/Cargo.toml delete mode 100644 integration-tests/multi-contract-caller/subber/lib.rs delete mode 100755 integration-tests/multisig/.gitignore delete mode 100755 integration-tests/multisig/Cargo.toml delete mode 100755 integration-tests/multisig/lib.rs delete mode 100755 integration-tests/payment-channel/.gitignore delete mode 100755 integration-tests/payment-channel/Cargo.toml delete mode 100755 integration-tests/payment-channel/lib.rs delete mode 100755 integration-tests/psp22-extension/.gitignore delete mode 100755 integration-tests/psp22-extension/Cargo.toml delete mode 100644 integration-tests/psp22-extension/README.md delete mode 100755 integration-tests/psp22-extension/lib.rs delete mode 100644 integration-tests/psp22-extension/runtime/psp22-extension-example.rs delete mode 100755 integration-tests/rand-extension/.gitignore delete mode 100755 integration-tests/rand-extension/Cargo.toml delete mode 100644 integration-tests/rand-extension/README.md delete mode 100755 integration-tests/rand-extension/lib.rs delete mode 100644 integration-tests/rand-extension/runtime/chain-extension-example.rs delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/Cargo.toml delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/lib.rs delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml delete mode 100644 integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs delete mode 100644 integration-tests/trait-erc20/.gitignore delete mode 100644 integration-tests/trait-erc20/Cargo.toml delete mode 100644 integration-tests/trait-erc20/lib.rs delete mode 100644 integration-tests/trait-flipper/.gitignore delete mode 100644 integration-tests/trait-flipper/Cargo.toml delete mode 100644 integration-tests/trait-flipper/lib.rs delete mode 100644 integration-tests/trait-incrementer/.gitignore delete mode 100644 integration-tests/trait-incrementer/Cargo.toml delete mode 100644 integration-tests/trait-incrementer/lib.rs delete mode 100644 integration-tests/trait-incrementer/traits/.gitignore delete mode 100644 integration-tests/trait-incrementer/traits/Cargo.toml delete mode 100644 integration-tests/trait-incrementer/traits/lib.rs delete mode 100644 integration-tests/upgradeable-contracts/README.md delete mode 100644 integration-tests/upgradeable-contracts/delegator/.gitignore delete mode 100644 integration-tests/upgradeable-contracts/delegator/Cargo.toml delete mode 100644 integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore delete mode 100644 integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml delete mode 100644 integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs delete mode 100644 integration-tests/upgradeable-contracts/delegator/lib.rs delete mode 100644 integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml delete mode 100644 integration-tests/upgradeable-contracts/set-code-hash/lib.rs delete mode 100644 integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml delete mode 100644 integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs delete mode 100644 integration-tests/wildcard-selector/.gitignore delete mode 100644 integration-tests/wildcard-selector/Cargo.toml delete mode 100644 integration-tests/wildcard-selector/lib.rs diff --git a/integration-tests/basic-contract-caller/.gitignore b/integration-tests/basic-contract-caller/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/basic-contract-caller/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/basic-contract-caller/Cargo.toml b/integration-tests/basic-contract-caller/Cargo.toml deleted file mode 100755 index 0781400a2f4..00000000000 --- a/integration-tests/basic-contract-caller/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "basic-contract-caller" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -# Note: We **need** to specify the `ink-as-dependency` feature. -# -# If we don't we will end up with linking errors! -other-contract = { path = "other-contract", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - # Note: The metadata generation step requires `std`. If we don't specify this the metadata - # generation for our contract will fail! - "other-contract/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/basic-contract-caller/lib.rs b/integration-tests/basic-contract-caller/lib.rs deleted file mode 100755 index f988caabf41..00000000000 --- a/integration-tests/basic-contract-caller/lib.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod basic_contract_caller { - /// We import the generated `ContractRef` of our other contract. - /// - /// Note that the other contract must have re-exported it (`pub use - /// OtherContractRef`) for us to have access to it. - use other_contract::OtherContractRef; - - #[ink(storage)] - pub struct BasicContractCaller { - /// We specify that our contract will store a reference to the `OtherContract`. - other_contract: OtherContractRef, - } - - impl BasicContractCaller { - /// In order to use the `OtherContract` we first need to **instantiate** it. - /// - /// To do this we will use the uploaded `code_hash` of `OtherContract`. - #[ink(constructor)] - pub fn new(other_contract_code_hash: Hash) -> Self { - let other_contract = OtherContractRef::new(true) - .code_hash(other_contract_code_hash) - .endowment(0) - .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) - .instantiate(); - - Self { other_contract } - } - - /// Using the `ContractRef` we can call all the messages of the `OtherContract` as - /// if they were normal Rust methods (because at the end of the day, they - /// are!). - #[ink(message)] - pub fn flip_and_get(&mut self) -> bool { - self.other_contract.flip(); - self.other_contract.get() - } - } -} diff --git a/integration-tests/basic-contract-caller/other-contract/.gitignore b/integration-tests/basic-contract-caller/other-contract/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/basic-contract-caller/other-contract/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/basic-contract-caller/other-contract/Cargo.toml b/integration-tests/basic-contract-caller/other-contract/Cargo.toml deleted file mode 100755 index c0b4748e22b..00000000000 --- a/integration-tests/basic-contract-caller/other-contract/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "other-contract" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/basic-contract-caller/other-contract/lib.rs b/integration-tests/basic-contract-caller/other-contract/lib.rs deleted file mode 100755 index 53e51019476..00000000000 --- a/integration-tests/basic-contract-caller/other-contract/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -/// Re-export the `ContractRef` generated by the ink! codegen. -/// -/// This let's other crates which pull this contract in as a dependency to interact -/// with this contract in a type-safe way. -pub use self::other_contract::OtherContractRef; - -#[ink::contract] -mod other_contract { - - #[ink(storage)] - pub struct OtherContract { - value: bool, - } - - impl OtherContract { - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { value: init_value } - } - - #[ink(message)] - pub fn flip(&mut self) { - self.value = !self.value; - } - - #[ink(message)] - pub fn get(&self) -> bool { - self.value - } - } -} diff --git a/integration-tests/call-builder-return-value/Cargo.toml b/integration-tests/call-builder-return-value/Cargo.toml deleted file mode 100755 index b4723f34a92..00000000000 --- a/integration-tests/call-builder-return-value/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "call_builder_return_value" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -incrementer = { path = "../incrementer", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "incrementer/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs deleted file mode 100755 index 0f0e3d27b7a..00000000000 --- a/integration-tests/call-builder-return-value/lib.rs +++ /dev/null @@ -1,298 +0,0 @@ -//! This contract is used to ensure that the values returned by cross contract calls using -//! the [`CallBuilder`](`ink::env::call::CallBuilder`) are properly decoded. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod call_builder { - use ink::{ - env::{ - call::{ - ExecutionInput, - Selector, - }, - DefaultEnvironment, - }, - prelude::{ - format, - string::{ - String, - ToString, - }, - }, - }; - - #[ink(storage)] - #[derive(Default)] - pub struct CallBuilderReturnValue { - /// Since we're going to `DelegateCall` into the `incrementer` contract, we need - /// to make sure our storage layout matches. - value: i32, - } - - impl CallBuilderReturnValue { - #[ink(constructor)] - pub fn new(value: i32) -> Self { - Self { value } - } - - /// Delegate a call to the given contract/selector and return the result. - #[ink(message)] - pub fn delegate_call(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { - use ink::env::call::build_call; - - build_call::() - .delegate(code_hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .invoke() - } - - /// Delegate call to the given contract/selector and attempt to decode the return - /// value into an `i8`. - #[ink(message)] - pub fn delegate_call_short_return_type( - &mut self, - code_hash: Hash, - selector: [u8; 4], - ) -> Result { - use ink::env::call::build_call; - - let result = build_call::() - .delegate(code_hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .try_invoke(); - - match result { - Ok(Ok(value)) => Ok(value), - Ok(Err(err)) => Err(format!("LangError: {:?}", err)), - Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), - Err(err) => Err(format!("Env Error: {:?}", err)), - } - } - - /// Forward a call to the given contract/selector and return the result. - #[ink(message)] - pub fn forward_call(&mut self, address: AccountId, selector: [u8; 4]) -> i32 { - use ink::env::call::build_call; - - build_call::() - .call(address) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .invoke() - } - - /// Forward call to the given contract/selector and attempt to decode the return - /// value into an `i8`. - #[ink(message)] - pub fn forward_call_short_return_type( - &mut self, - address: AccountId, - selector: [u8; 4], - ) -> Result { - use ink::env::call::build_call; - - let result = build_call::() - .call(address) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .try_invoke(); - - match result { - Ok(Ok(value)) => Ok(value), - Ok(Err(err)) => Err(format!("LangError: {:?}", err)), - Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), - Err(err) => Err(format!("Env Error: {:?}", err)), - } - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use incrementer::IncrementerRef; - use ink_e2e::{ - ChainBackend, - ContractsBackend, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_delegate_call_return_value_returns_correct_value< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let expected_value = 42; - let mut constructor = CallBuilderReturnValueRef::new(expected_value); - let call_builder = client - .instantiate("call_builder_return_value", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("incrementer", &origin) - .submit() - .await - .expect("upload `incrementer` failed") - .code_hash; - - let selector = ink::selector_bytes!("get"); - let call = call_builder_call.delegate_call(code_hash, selector); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Client failed to call `call_builder::invoke`.") - .return_value(); - - assert_eq!( - call_result, expected_value, - "Decoded an unexpected value from the call." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_delegate_call_return_value_errors_if_return_data_too_long< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderReturnValueRef::new(42); - let call_builder = client - .instantiate("call_builder_return_value", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("incrementer", &origin) - .submit() - .await - .expect("upload `incrementer` failed") - .code_hash; - - let selector = ink::selector_bytes!("get"); - let call = - call_builder_call.delegate_call_short_return_type(code_hash, selector); - let call_result: Result = - client.call(&origin, &call).dry_run().await?.return_value(); - - assert!( - call_result.is_err(), - "Should fail of decoding an `i32` into an `i8`" - ); - assert_eq!( - "Decode Error".to_string(), - call_result.unwrap_err(), - "Should fail to decode short type if bytes remain from return data." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_forward_call_return_value_returns_correct_value< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderReturnValueRef::new(0); - let call_builder = client - .instantiate("call_builder_return_value", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let expected_value = 42; - let mut incrementer_constructor = IncrementerRef::new(expected_value); - let incrementer = client - .instantiate("incrementer", &origin, &mut incrementer_constructor) - .submit() - .await - .expect("instantiate failed"); - - let selector = ink::selector_bytes!("get"); - let call = call_builder_call.forward_call(incrementer.account_id, selector); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Client failed to call `call_builder::invoke`.") - .return_value(); - - assert_eq!( - call_result, expected_value, - "Decoded an unexpected value from the call." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_forward_call_return_value_errors_if_return_data_too_long< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderReturnValueRef::new(0); - let call_builder = client - .instantiate("call_builder_return_value", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let expected_value = 42; - let mut incrementer_constructor = IncrementerRef::new(expected_value); - let incrementer = client - .instantiate("incrementer", &origin, &mut incrementer_constructor) - .submit() - .await - .expect("instantiate failed"); - - let selector = ink::selector_bytes!("get"); - let call = call_builder_call - .forward_call_short_return_type(incrementer.account_id, selector); - let call_result: Result = - client.call(&origin, &call).dry_run().await?.return_value(); - - assert!( - call_result.is_err(), - "Should fail of decoding an `i32` into an `i8`" - ); - assert_eq!( - "Decode Error".to_string(), - call_result.unwrap_err(), - "Should fail to decode short type if bytes remain from return data." - ); - - Ok(()) - } - } -} diff --git a/integration-tests/call-runtime/.gitignore b/integration-tests/call-runtime/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/call-runtime/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/call-runtime/Cargo.toml b/integration-tests/call-runtime/Cargo.toml deleted file mode 100644 index 1876308e0fe..00000000000 --- a/integration-tests/call-runtime/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "call-runtime" -version = "4.0.0" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -# Substrate -# -# We need to explicitly turn off some of the `sp-io` features, to avoid conflicts -# (especially for global allocator). -# -# See also: https://substrate.stackexchange.com/questions/4733/error-when-compiling-a-contract-using-the-xcm-chain-extension. -sp-io = { version = "23.0.0", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] } -sp-runtime = { version = "24.0.0", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "sp-runtime/std", - "sp-io/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/call-runtime/README.md b/integration-tests/call-runtime/README.md deleted file mode 100644 index acf3c726f21..00000000000 --- a/integration-tests/call-runtime/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# `call-runtime` example - -## What is this example about? - -It demonstrates how to call a runtime dispatchable from an ink! contract. - -## Chain-side configuration - -To integrate this example into Substrate you need to adjust pallet contracts configuration in your runtime: - ```rust - // In your node's runtime configuration file (runtime.rs) - impl pallet_contracts::Config for Runtime { - … - // `Everything` or anything that will allow for the `Balances::transfer` extrinsic. - type CallFilter = frame_support::traits::Everything; - type UnsafeUnstableInterface = ConstBool; - … - } - ``` - -## Comparison to `ChainExtension` - -Just as a chain extension, `call_runtime` API allows contracts for direct calling to the runtime. -You can trigger any extrinsic that is not forbidden by `pallet_contracts::Config::CallFilter`. -Consider writing a chain extension if you need to perform one of the following tasks: -- Return data. -- Provide functionality **exclusively** to contracts. -- Provide custom weights. -- Avoid the need to keep the `Call` data structure stable. diff --git a/integration-tests/call-runtime/lib.rs b/integration-tests/call-runtime/lib.rs deleted file mode 100644 index 0fa1830003f..00000000000 --- a/integration-tests/call-runtime/lib.rs +++ /dev/null @@ -1,284 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use ink::primitives::AccountId; -use sp_runtime::MultiAddress; - -/// A part of the runtime dispatchable API. -/// -/// For now, `ink!` doesn't provide any support for exposing the real `RuntimeCall` enum, -/// which fully describes the composed API of all the pallets present in runtime. Hence, -/// in order to use `call-runtime` functionality, we have to provide at least a partial -/// object, which correctly encodes the target extrinsic. -/// -/// You can investigate the full `RuntimeCall` definition by either expanding -/// `construct_runtime!` macro application or by using secondary tools for reading chain -/// metadata, like `subxt`. -#[ink::scale_derive(Encode)] -enum RuntimeCall { - /// This index can be found by investigating runtime configuration. You can check the - /// pallet order inside `construct_runtime!` block and read the position of your - /// pallet (0-based). - /// - /// - /// [See here for more.](https://substrate.stackexchange.com/questions/778/how-to-get-pallet-index-u8-of-a-pallet-in-runtime) - #[codec(index = 4)] - Balances(BalancesCall), -} - -#[ink::scale_derive(Encode)] -enum BalancesCall { - /// This index can be found by investigating the pallet dispatchable API. In your - /// pallet code, look for `#[pallet::call]` section and check - /// `#[pallet::call_index(x)]` attribute of the call. If these attributes are - /// missing, use source-code order (0-based). - #[codec(index = 0)] - Transfer { - dest: MultiAddress, - #[codec(compact)] - value: u128, - }, -} - -#[ink::contract] -mod runtime_call { - use crate::{ - BalancesCall, - RuntimeCall, - }; - - use ink::env::Error as EnvError; - - /// A trivial contract with a single message, that uses `call-runtime` API for - /// performing native token transfer. - #[ink(storage)] - #[derive(Default)] - pub struct RuntimeCaller; - - #[derive(Debug, PartialEq, Eq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum RuntimeError { - CallRuntimeFailed, - } - - impl From for RuntimeError { - fn from(e: EnvError) -> Self { - use ink::env::ReturnErrorCode; - match e { - EnvError::ReturnError(ReturnErrorCode::CallRuntimeFailed) => { - RuntimeError::CallRuntimeFailed - } - _ => panic!("Unexpected error from `pallet-contracts`."), - } - } - } - - impl RuntimeCaller { - /// The constructor is `payable`, so that during instantiation it can be given - /// some tokens that will be further transferred with - /// `transfer_through_runtime` message. - #[ink(constructor, payable)] - pub fn new() -> Self { - Default::default() - } - - /// Tries to transfer `value` from the contract's balance to `receiver`. - /// - /// Fails if: - /// - called in the off-chain environment - /// - the chain forbids contracts to call `Balances::transfer` (`CallFilter` is - /// too restrictive) - /// - after the transfer, `receiver` doesn't have at least existential deposit - /// - the contract doesn't have enough balance - #[ink(message)] - pub fn transfer_through_runtime( - &mut self, - receiver: AccountId, - value: Balance, - ) -> Result<(), RuntimeError> { - self.env() - .call_runtime(&RuntimeCall::Balances(BalancesCall::Transfer { - dest: receiver.into(), - value, - })) - .map_err(Into::into) - } - - /// Tries to trigger `call_runtime` API with rubbish data. - /// - /// # Note - /// - /// This message is for testing purposes only. - #[ink(message)] - pub fn call_nonexistent_extrinsic(&mut self) -> Result<(), RuntimeError> { - self.env().call_runtime(&()).map_err(Into::into) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - ChainBackend, - ContractsBackend, - }; - - use ink::{ - env::{ - test::default_accounts, - DefaultEnvironment, - }, - primitives::AccountId, - }; - - type E2EResult = Result>; - - /// The base number of indivisible units for balances on the - /// `substrate-contracts-node`. - const UNIT: Balance = 1_000_000_000_000; - - /// The contract will be given 1000 tokens during instantiation. - const CONTRACT_BALANCE: Balance = 1_000 * UNIT; - - /// The receiver will get enough funds to have the required existential deposit. - /// - /// If your chain has this threshold higher, increase the transfer value. - const TRANSFER_VALUE: Balance = 1 / 10 * UNIT; - - /// An amount that is below the existential deposit, so that a transfer to an - /// empty account fails. - /// - /// Must not be zero, because such an operation would be a successful no-op. - const INSUFFICIENT_TRANSFER_VALUE: Balance = 1; - - /// Positive case scenario: - /// - the call is valid - /// - the call execution succeeds - #[ink_e2e::test] - async fn transfer_with_call_runtime_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = RuntimeCallerRef::new(); - let contract = client - .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) - .value(CONTRACT_BALANCE) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let receiver: AccountId = default_accounts::().bob; - - let contract_balance_before = client - .free_balance(contract.account_id) - .await - .expect("Failed to get account balance"); - let receiver_balance_before = client - .free_balance(receiver) - .await - .expect("Failed to get account balance"); - - // when - let transfer_message = - call.transfer_through_runtime(receiver, TRANSFER_VALUE); - - let call_res = client - .call(&ink_e2e::alice(), &transfer_message) - .submit() - .await - .expect("call failed"); - - assert!(call_res.return_value().is_ok()); - - // then - let contract_balance_after = client - .free_balance(contract.account_id) - .await - .expect("Failed to get account balance"); - let receiver_balance_after = client - .free_balance(receiver) - .await - .expect("Failed to get account balance"); - - assert_eq!( - contract_balance_before, - contract_balance_after + TRANSFER_VALUE - ); - assert_eq!( - receiver_balance_before, - receiver_balance_after - TRANSFER_VALUE - ); - - Ok(()) - } - - /// Negative case scenario: - /// - the call is valid - /// - the call execution fails - #[ink_e2e::test] - async fn transfer_with_call_runtime_fails_when_execution_fails< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = RuntimeCallerRef::new(); - let contract = client - .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) - .value(CONTRACT_BALANCE) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let receiver: AccountId = default_accounts::().bob; - - // when - let transfer_message = - call.transfer_through_runtime(receiver, INSUFFICIENT_TRANSFER_VALUE); - - let call_res = client - .call(&ink_e2e::alice(), &transfer_message) - .dry_run() - .await? - .return_value(); - - // then - assert!(matches!(call_res, Err(RuntimeError::CallRuntimeFailed))); - - Ok(()) - } - - /// Negative case scenario: - /// - the call is invalid - #[ink_e2e::test] - async fn transfer_with_call_runtime_fails_when_call_is_invalid< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = RuntimeCallerRef::new(); - let contract = client - .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) - .value(CONTRACT_BALANCE) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let transfer_message = call.call_nonexistent_extrinsic(); - - let call_res = client - .call(&ink_e2e::alice(), &transfer_message) - .dry_run() - .await; - - // then - assert!(call_res.is_err()); - - Ok(()) - } - } -} diff --git a/integration-tests/combined-extension/.gitignore b/integration-tests/combined-extension/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/combined-extension/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/combined-extension/Cargo.toml b/integration-tests/combined-extension/Cargo.toml deleted file mode 100755 index e93251606a0..00000000000 --- a/integration-tests/combined-extension/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "combined_extension" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } -psp22_extension = { path = "../psp22-extension", default-features = false, features = ["ink-as-dependency"] } -rand_extension = { path = "../rand-extension", default-features = false, features = ["ink-as-dependency"] } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "psp22_extension/std", - "rand_extension/std", -] -ink-as-dependency = [ - "psp22_extension/ink-as-dependency", - "rand_extension/ink-as-dependency", -] diff --git a/integration-tests/combined-extension/README.md b/integration-tests/combined-extension/README.md deleted file mode 100644 index f71d8cbaeaa..00000000000 --- a/integration-tests/combined-extension/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Combined Chain Extension Example - -## What is this example about? - -It demonstrates how to combine several chain extensions and call them from ink!. - -See [this chapter](https://use.ink/macros-attributes/chain-extension) -in our ink! documentation for more details about chain extensions. - - -This example uses two chain extensions, `FetchRandom`(from [rand-extension](../rand-extension)) -and `Psp22Extension`(from [psp22-extension](../psp22-extension)) defined in other examples. -The example shows how to combine two chain extensions together -and use them in the contract along with each other. -Also example shows how to mock each chain extension for testing. - -This example doesn't show how to define a chain extension and how -to implement in on the runtime side. For that purpose, you can -check the two examples mentioned above. diff --git a/integration-tests/combined-extension/lib.rs b/integration-tests/combined-extension/lib.rs deleted file mode 100755 index 76824771354..00000000000 --- a/integration-tests/combined-extension/lib.rs +++ /dev/null @@ -1,189 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use ink::env::{ - DefaultEnvironment, - Environment, -}; -use psp22_extension::Psp22Extension; -use rand_extension::{ - FetchRandom, - RandomReadErr, -}; - -ink::combine_extensions! { - /// This extension combines the [`FetchRandom`] and [`Psp22Extension`] extensions. - /// It is possible to combine any number of extensions in this way. - /// - /// This structure is an instance that is returned by the `self.env().extension()` call. - pub struct CombinedChainExtension { - /// The instance of the [`Psp22Extension`] chain extension. - /// - /// It provides you access to `PSP22` functionality. - pub psp22: Psp22Extension, - /// The instance of the [`FetchRandom`] chain extension. - /// - /// It provides you access to randomness functionality. - pub rand: FetchRandom, - } -} - -/// An environment using default ink environment types, with PSP-22 extension included -#[derive(Debug, Clone, PartialEq, Eq)] -#[ink::scale_derive(TypeInfo)] -pub enum CustomEnvironment {} - -/// We use the same types and values as for [`DefaultEnvironment`] except the -/// [`Environment::ChainExtension`] type. -impl Environment for CustomEnvironment { - const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; - - type AccountId = ::AccountId; - type Balance = ::Balance; - type Hash = ::Hash; - type Timestamp = ::Timestamp; - type BlockNumber = ::BlockNumber; - - /// Setting up the combined chain extension as a primary extension. - /// - /// The `self.env().extension()` call returns the [`CombinedInstance`] - /// that provides access two both chain extensions. - type ChainExtension = CombinedChainExtension; -} - -#[ink::contract(env = crate::CustomEnvironment)] -mod combined_extension { - use super::*; - use psp22_extension::Psp22Error; - - /// Defines the storage of our contract. - /// - /// The example shows how to call each extension and test it, - /// so we don't need any state to save. - #[ink(storage)] - #[derive(Default)] - pub struct CombinedExtensionContract; - - impl CombinedExtensionContract { - /// Constructor that initializes empty storage. - #[ink(constructor)] - pub fn new() -> Self { - Self {} - } - - /// Returns the random value from extension. - #[ink(message)] - pub fn get_rand(&self) -> Result<[u8; 32], RandomReadErr> { - self.env().extension().rand.fetch_random([0; 32] /* seed */) - } - - /// Returns the total supply from PSP22 extension. - #[ink(message)] - pub fn get_total_supply(&self) -> Result { - self.env().extension().psp22.total_supply(0) - } - } - - /// Unit tests in Rust are normally defined within such a `#[cfg(test)]` - #[cfg(test)] - mod tests { - /// Imports all the definitions from the outer scope so we can use them here. - use super::*; - - const RANDOM_VALUE: [u8; 32] = [3; 32]; - - /// Mocking the random extension to return results that we want in the tests. - struct MockedRandExtension; - impl ink::env::test::ChainExtension for MockedRandExtension { - fn ext_id(&self) -> u16 { - // It is identifier used by [`rand_extension::FetchRandom`] extension. - 666 - } - - fn call( - &mut self, - _func_id: u16, - _input: &[u8], - output: &mut Vec, - ) -> u32 { - ink::scale::Encode::encode_to(&RANDOM_VALUE, output); - 0 - } - } - - #[ink::test] - fn rand_chain_extension_works() { - let contract = CombinedExtensionContract::new(); - - // given - let result = std::panic::catch_unwind(|| contract.get_rand()); - // The call to random extension should fail because it is not registered. - assert!(result.is_err()); - - // when - ink::env::test::register_chain_extension(MockedRandExtension); - - // then - assert_eq!(contract.get_rand(), Ok(RANDOM_VALUE)); - } - - const TOTAL_SUPPLY: u128 = 1377; - - /// Mocking the PSP22 extension to return results that we want in the tests. - /// - /// Because this extension has many methods, we want to implement only one of - /// them: - /// - `total_supply` with corresponding `func_id` - `0x162d`. - struct MockedPSP22Extension; - impl ink::env::test::ChainExtension for MockedPSP22Extension { - fn ext_id(&self) -> u16 { - // It is identifier used by [`psp22_extension::Psp22Extension`] extension. - 13 - } - - fn call(&mut self, func_id: u16, _input: &[u8], output: &mut Vec) -> u32 { - match func_id { - 0x162d /* `func_id` of the `total_supply` function */ => { - ink::scale::Encode::encode_to(&TOTAL_SUPPLY, output); - 0 - } - _ => { - 1 - } - } - } - } - - #[ink::test] - fn psp22_chain_extension_works() { - let contract = CombinedExtensionContract::new(); - - // given - let result = std::panic::catch_unwind(|| contract.get_total_supply()); - // The call to PSP22 extension should fail because it is not registered. - assert!(result.is_err()); - - // when - ink::env::test::register_chain_extension(MockedPSP22Extension); - - // then - assert_eq!(contract.get_total_supply(), Ok(TOTAL_SUPPLY)); - } - - #[ink::test] - fn both_chain_extensions_work() { - let contract = CombinedExtensionContract::new(); - - // given - assert!(std::panic::catch_unwind(|| contract.get_rand()).is_err()); - assert!(std::panic::catch_unwind(|| { contract.get_total_supply() }).is_err()); - - // when - ink::env::test::register_chain_extension(MockedRandExtension); - ink::env::test::register_chain_extension(MockedPSP22Extension); - - // then - assert_eq!(contract.get_rand(), Ok(RANDOM_VALUE)); - assert_eq!(contract.get_total_supply(), Ok(TOTAL_SUPPLY)); - } - } -} diff --git a/integration-tests/conditional-compilation/.gitignore b/integration-tests/conditional-compilation/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/conditional-compilation/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/conditional-compilation/Cargo.toml b/integration-tests/conditional-compilation/Cargo.toml deleted file mode 100755 index 2c2b7c3566d..00000000000 --- a/integration-tests/conditional-compilation/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "conditional-compilation" -version = "0.1.0" -authors = ["Parity Technologies "] -edition = "2021" - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] -# user-defined features -foo = [] -bar = [] diff --git a/integration-tests/conditional-compilation/lib.rs b/integration-tests/conditional-compilation/lib.rs deleted file mode 100755 index 8619493b9d4..00000000000 --- a/integration-tests/conditional-compilation/lib.rs +++ /dev/null @@ -1,161 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::trait_definition] -pub trait Flip { - /// Flips the current value of the Flipper's boolean. - #[ink(message)] - fn flip(&mut self); - - /// Returns the current value of the Flipper's boolean. - #[ink(message)] - fn get(&self) -> bool; - - #[cfg(feature = "foo")] - #[ink(message)] - fn push_foo(&mut self, value: bool); -} - -#[ink::contract] -pub mod conditional_compilation { - use super::Flip; - - /// Feature gated event - #[cfg(feature = "foo")] - #[ink(event)] - pub struct Changes { - // attributing event field with `cfg` is not allowed - new_value: bool, - #[ink(topic)] - by: AccountId, - } - - /// Feature gated event - #[cfg(feature = "bar")] - #[ink(event)] - pub struct ChangesDated { - // attributing event field with `cfg` is not allowed - new_value: bool, - #[ink(topic)] - by: AccountId, - when: BlockNumber, - } - - #[ink(storage)] - pub struct ConditionalCompilation { - value: bool, - } - - impl ConditionalCompilation { - /// Creates a new flipper smart contract initialized to `false`. - #[ink(constructor)] - pub fn new() -> Self { - Self { - value: Default::default(), - } - } - - /// Constructor that is included when `foo` is enabled - #[cfg(feature = "foo")] - #[ink(constructor)] - pub fn new_foo(value: bool) -> Self { - Self { value } - } - - /// Constructor that is included when `bar` is enabled - #[cfg(feature = "bar")] - #[ink(constructor)] - pub fn new_bar(value: bool) -> Self { - Self { value } - } - - /// Constructor that is included with either `foo` or `bar` features enabled - #[cfg(feature = "foo")] - #[cfg(feature = "bar")] - #[ink(constructor)] - pub fn new_foo_bar(value: bool) -> Self { - Self { value } - } - - #[cfg(feature = "foo")] - #[ink(message)] - pub fn inherent_flip_foo(&mut self) { - self.value = !self.value; - let caller = Self::env().caller(); - Self::env().emit_event(Changes { - new_value: self.value, - by: caller, - }); - } - - #[cfg(feature = "bar")] - #[ink(message)] - pub fn inherent_flip_bar(&mut self) { - let caller = Self::env().caller(); - let block_number = Self::env().block_number(); - self.value = !self.value; - Self::env().emit_event(ChangesDated { - new_value: self.value, - by: caller, - when: block_number, - }); - } - } - - impl Flip for ConditionalCompilation { - #[ink(message)] - fn flip(&mut self) { - self.value = !self.value; - } - - #[ink(message)] - fn get(&self) -> bool { - self.value - } - - /// Feature gated mutating message - #[cfg(feature = "foo")] - #[ink(message)] - fn push_foo(&mut self, value: bool) { - let caller = Self::env().caller(); - Self::env().emit_event(Changes { - new_value: value, - by: caller, - }); - self.value = value; - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - let flipper = ConditionalCompilation::new(); - assert!(!flipper.get()); - } - - #[ink::test] - fn it_works() { - let mut flipper = ConditionalCompilation::new(); - // Can call using universal call syntax using the trait. - assert!(!::get(&flipper)); - ::flip(&mut flipper); - // Normal call syntax possible to as long as the trait is in scope. - assert!(flipper.get()); - } - - #[cfg(feature = "foo")] - #[ink::test] - fn foo_works() { - let mut flipper = ConditionalCompilation::new_foo(false); - - flipper.inherent_flip_foo(); - assert!(flipper.get()); - - ::push_foo(&mut flipper, false); - assert!(!flipper.get()) - } - } -} diff --git a/integration-tests/contract-storage/.gitignore b/integration-tests/contract-storage/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/contract-storage/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/contract-storage/Cargo.toml b/integration-tests/contract-storage/Cargo.toml deleted file mode 100755 index efbbd475132..00000000000 --- a/integration-tests/contract-storage/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "contract-storage" -version = "4.2.0" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/contract-storage/e2e_tests.rs b/integration-tests/contract-storage/e2e_tests.rs deleted file mode 100644 index d5655139bd8..00000000000 --- a/integration-tests/contract-storage/e2e_tests.rs +++ /dev/null @@ -1,122 +0,0 @@ -use super::contract_storage::*; -use ink_e2e::ContractsBackend; - -type E2EResult = std::result::Result>; - -#[ink_e2e::test] -async fn get_contract_storage_consumes_entire_buffer( - mut client: Client, -) -> E2EResult<()> { - // given - let mut constructor = ContractStorageRef::new(); - let contract = client - .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - // when - let result = client - .call( - &ink_e2e::alice(), - &call.set_and_get_storage_all_data_consumed(), - ) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - assert!(result.is_ok()); - - Ok(()) -} - -#[ink_e2e::test] -async fn get_contract_storage_fails_when_extra_data( - mut client: Client, -) -> E2EResult<()> { - // given - let mut constructor = ContractStorageRef::new(); - let contract = client - .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - // when - let result = client - .call( - &ink_e2e::alice(), - &call.set_and_get_storage_partial_data_consumed(), - ) - .submit() - .await; - - assert!( - result.is_err(), - "Expected the contract to revert when only partially consuming the buffer" - ); - - Ok(()) -} - -#[ink_e2e::test] -async fn take_contract_storage_consumes_entire_buffer( - mut client: Client, -) -> E2EResult<()> { - // given - let mut constructor = ContractStorageRef::new(); - let contract = client - .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - // when - let result = client - .call( - &ink_e2e::alice(), - &call.set_and_take_storage_all_data_consumed(), - ) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - assert!(result.is_ok()); - - Ok(()) -} - -#[ink_e2e::test] -async fn take_contract_storage_fails_when_extra_data( - mut client: Client, -) -> E2EResult<()> { - // given - let mut constructor = ContractStorageRef::new(); - let contract = client - .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - // when - let result = client - .call( - &ink_e2e::alice(), - &call.set_and_take_storage_partial_data_consumed(), - ) - .submit() - .await; - - assert!( - result.is_err(), - "Expected the contract to revert when only partially consuming the buffer" - ); - - Ok(()) -} diff --git a/integration-tests/contract-storage/lib.rs b/integration-tests/contract-storage/lib.rs deleted file mode 100755 index 1404711f13c..00000000000 --- a/integration-tests/contract-storage/lib.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! A smart contract to test reading and writing contract storage - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod contract_storage { - use ink::prelude::{ - format, - string::String, - }; - - /// A contract for testing reading and writing contract storage. - #[ink(storage)] - #[derive(Default)] - pub struct ContractStorage; - - impl ContractStorage { - #[ink(constructor)] - pub fn new() -> Self { - Self {} - } - - /// Read from the contract storage slot, consuming all the data from the buffer. - #[ink(message)] - pub fn set_and_get_storage_all_data_consumed(&self) -> Result<(), String> { - let key = 0u32; - let value = [0x42; 32]; - ink::env::set_contract_storage(&key, &value); - let loaded_value = ink::env::get_contract_storage(&key) - .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; - assert_eq!(loaded_value, Some(value)); - Ok(()) - } - - /// Read from the contract storage slot, only partially consuming data from the - /// buffer. - #[ink(message)] - pub fn set_and_get_storage_partial_data_consumed(&self) -> Result<(), String> { - let key = 0u32; - let value = [0x42; 32]; - ink::env::set_contract_storage(&key, &value); - // Only attempt to read the first byte (the `u8`) of the storage value data - let _loaded_value: Option = ink::env::get_contract_storage(&key) - .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; - Ok(()) - } - - /// Read from the contract storage slot, consuming all the data from the buffer. - #[ink(message)] - pub fn set_and_take_storage_all_data_consumed(&self) -> Result<(), String> { - let key = 0u32; - let value = [0x42; 32]; - ink::env::set_contract_storage(&key, &value); - let loaded_value = ink::env::take_contract_storage(&key) - .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; - assert_eq!(loaded_value, Some(value)); - Ok(()) - } - - /// Read from the contract storage slot, only partially consuming data from the - /// buffer. - #[ink(message)] - pub fn set_and_take_storage_partial_data_consumed(&self) -> Result<(), String> { - let key = 0u32; - let value = [0x42; 32]; - ink::env::set_contract_storage(&key, &value); - // Only attempt to read the first byte (the `u8`) of the storage value data - let _loaded_value: Option = ink::env::take_contract_storage(&key) - .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; - Ok(()) - } - } -} - -#[cfg(all(test, feature = "e2e-tests"))] -mod e2e_tests; diff --git a/integration-tests/contract-terminate/.gitignore b/integration-tests/contract-terminate/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/contract-terminate/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/contract-terminate/Cargo.toml b/integration-tests/contract-terminate/Cargo.toml deleted file mode 100644 index 23cc5b2e65a..00000000000 --- a/integration-tests/contract-terminate/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "contract_terminate" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/contract-terminate/lib.rs b/integration-tests/contract-terminate/lib.rs deleted file mode 100644 index 506118b1c72..00000000000 --- a/integration-tests/contract-terminate/lib.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! A smart contract which demonstrates behavior of the `self.env().terminate()` -//! function. It terminates itself once `terminate_me()` is called. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::contract] -pub mod just_terminates { - /// No storage is needed for this simple contract. - #[ink(storage)] - pub struct JustTerminate {} - - impl JustTerminate { - /// Creates a new instance of this contract. - #[ink(constructor)] - pub fn new() -> Self { - Self {} - } - - /// Terminates with the caller as beneficiary. - #[ink(message)] - pub fn terminate_me(&mut self) { - self.env().terminate_contract(self.env().caller()); - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn terminating_works() { - // given - let accounts = - ink::env::test::default_accounts::(); - let contract_id = ink::env::test::callee::(); - ink::env::test::set_caller::(accounts.alice); - ink::env::test::set_account_balance::( - contract_id, - 100, - ); - let mut contract = JustTerminate::new(); - - // when - let should_terminate = move || contract.terminate_me(); - - // then - ink::env::test::assert_contract_termination::( - should_terminate, - accounts.alice, - 100, - ); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_contract_terminates( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = JustTerminateRef::new(); - let contract = client - .instantiate("contract_terminate", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let terminate_me = call.terminate_me(); - let call_res = client - .call(&ink_e2e::alice(), &terminate_me) - .submit() - .await - .expect("terminate_me messages failed"); - - assert!( - call_res.return_data().is_empty(), - "Terminated contract never returns" - ); - - // then - assert!(call_res.contains_event("System", "KilledAccount")); - assert!(call_res.contains_event("Balances", "Withdraw")); - assert!(call_res.contains_event("Contracts", "Terminated")); - - Ok(()) - } - } -} diff --git a/integration-tests/contract-transfer/.gitignore b/integration-tests/contract-transfer/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/contract-transfer/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/contract-transfer/Cargo.toml b/integration-tests/contract-transfer/Cargo.toml deleted file mode 100644 index b0c9e9a94b9..00000000000 --- a/integration-tests/contract-transfer/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "contract_transfer" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/contract-transfer/lib.rs b/integration-tests/contract-transfer/lib.rs deleted file mode 100644 index 7fab52d44bc..00000000000 --- a/integration-tests/contract-transfer/lib.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! A smart contract which demonstrates behavior of the `self.env().transfer()` function. -//! It transfers some of it's balance to the caller. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::contract] -pub mod give_me { - /// No storage is needed for this simple contract. - #[ink(storage)] - pub struct GiveMe {} - - impl GiveMe { - /// Creates a new instance of this contract. - #[ink(constructor, payable)] - pub fn new() -> Self { - Self {} - } - - /// Transfers `value` amount of tokens to the caller. - /// - /// # Errors - /// - /// - Panics in case the requested transfer exceeds the contract balance. - /// - Panics in case the requested transfer would have brought this contract's - /// balance below the minimum balance (i.e. the chain's existential deposit). - /// - Panics in case the transfer failed for another reason. - #[ink(message)] - pub fn give_me(&mut self, value: Balance) { - ink::env::debug_println!("requested value: {}", value); - ink::env::debug_println!("contract balance: {}", self.env().balance()); - - assert!(value <= self.env().balance(), "insufficient funds!"); - - if self.env().transfer(self.env().caller(), value).is_err() { - panic!( - "requested transfer failed. this can be the case if the contract does not\ - have sufficient free funds or if the transfer would have brought the\ - contract's balance below minimum balance." - ) - } - } - - /// Asserts that the token amount sent as payment with this call - /// is exactly `10`. This method will fail otherwise, and the - /// transaction would then be reverted. - /// - /// # Note - /// - /// The method needs to be annotated with `payable`; only then it is - /// allowed to receive value as part of the call. - #[ink(message, payable, selector = 0xCAFEBABE)] - pub fn was_it_ten(&self) { - ink::env::debug_println!( - "received payment: {}", - self.env().transferred_value() - ); - assert!(self.env().transferred_value() == 10, "payment was not ten"); - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn transfer_works() { - // given - let contract_balance = 100; - let accounts = default_accounts(); - let mut give_me = create_contract(contract_balance); - - // when - set_sender(accounts.eve); - set_balance(accounts.eve, 0); - give_me.give_me(80); - - // then - assert_eq!(get_balance(accounts.eve), 80); - } - - #[ink::test] - #[should_panic(expected = "insufficient funds!")] - fn transfer_fails_insufficient_funds() { - // given - let contract_balance = 100; - let accounts = default_accounts(); - let mut give_me = create_contract(contract_balance); - - // when - set_sender(accounts.eve); - give_me.give_me(120); - - // then - // `give_me` must already have panicked here - } - - #[ink::test] - fn test_transferred_value() { - use ink::codegen::Env; - // given - let accounts = default_accounts(); - let give_me = create_contract(100); - let contract_account = give_me.env().account_id(); - - // when - // Push the new execution context which sets initial balances and - // sets Eve as the caller - set_balance(accounts.eve, 100); - set_balance(contract_account, 0); - set_sender(accounts.eve); - - // then - // we use helper macro to emulate method invocation coming with payment, - // and there must be no panic - ink::env::pay_with_call!(give_me.was_it_ten(), 10); - - // and - // balances should be changed properly - let contract_new_balance = get_balance(contract_account); - let caller_new_balance = get_balance(accounts.eve); - - assert_eq!(caller_new_balance, 100 - 10); - assert_eq!(contract_new_balance, 10); - } - - #[ink::test] - #[should_panic(expected = "payment was not ten")] - fn test_transferred_value_must_fail() { - // given - let accounts = default_accounts(); - let give_me = create_contract(100); - - // when - // Push the new execution context which sets Eve as caller and - // the `mock_transferred_value` as the value which the contract - // will see as transferred to it. - set_sender(accounts.eve); - ink::env::test::set_value_transferred::(13); - - // then - give_me.was_it_ten(); - } - - /// Creates a new instance of `GiveMe` with `initial_balance`. - /// - /// Returns the `contract_instance`. - fn create_contract(initial_balance: Balance) -> GiveMe { - let accounts = default_accounts(); - set_sender(accounts.alice); - set_balance(contract_id(), initial_balance); - GiveMe::new() - } - - fn contract_id() -> AccountId { - ink::env::test::callee::() - } - - fn set_sender(sender: AccountId) { - ink::env::test::set_caller::(sender); - } - - fn default_accounts( - ) -> ink::env::test::DefaultAccounts { - ink::env::test::default_accounts::() - } - - fn set_balance(account_id: AccountId, balance: Balance) { - ink::env::test::set_account_balance::( - account_id, balance, - ) - } - - fn get_balance(account_id: AccountId) -> Balance { - ink::env::test::get_account_balance::( - account_id, - ) - .expect("Cannot get account balance") - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - ChainBackend, - ContractsBackend, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_sending_value_to_give_me_must_fail( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = GiveMeRef::new(); - let contract = client - .instantiate("contract_transfer", &ink_e2e::alice(), &mut constructor) - .value(1000) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let transfer = call.give_me(120); - - let call_res = client - .call(&ink_e2e::bob(), &transfer) - .value(10) - .submit() - .await; - - // then - if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_res { - assert!(dry_run.debug_message.contains("paid an unpayable message")) - } else { - panic!("Paying an unpayable message should fail") - } - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_contract_must_transfer_value_to_sender( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = GiveMeRef::new(); - let contract = client - .instantiate("contract_transfer", &ink_e2e::bob(), &mut constructor) - .value(1337) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let balance_before: Balance = client - .free_balance(contract.account_id.clone()) - .await - .expect("getting balance failed"); - - // when - let transfer = call.give_me(120); - - let call_res = client - .call(&ink_e2e::eve(), &transfer) - .submit() - .await - .expect("call failed"); - - // then - assert!(call_res.debug_message().contains("requested value: 120\n")); - - let balance_after: Balance = client - .free_balance(contract.account_id.clone()) - .await - .expect("getting balance failed"); - assert_eq!(balance_before - balance_after, 120); - - Ok(()) - } - } -} diff --git a/integration-tests/custom-allocator/.gitignore b/integration-tests/custom-allocator/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/custom-allocator/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/custom-allocator/Cargo.toml b/integration-tests/custom-allocator/Cargo.toml deleted file mode 100755 index e3315fd7ca9..00000000000 --- a/integration-tests/custom-allocator/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "custom-allocator" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -# We're going to use a different allocator than the one provided by ink!. To do that we -# first need to disable the included memory allocator. -ink = { path = "../../crates/ink", default-features = false, features = ["no-allocator"] } - -# This is going to be our new global memory allocator. -dlmalloc = {version = "0.2", default-features = false, features = ["global"] } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/custom-allocator/lib.rs b/integration-tests/custom-allocator/lib.rs deleted file mode 100755 index 981ab3ba4e9..00000000000 --- a/integration-tests/custom-allocator/lib.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! # Custom Allocator -//! -//! This example demonstrates how to opt-out of the ink! provided global memory allocator. -//! -//! We will use [`dlmalloc`](https://github.com/alexcrichton/dlmalloc-rs) instead. -//! -//! ## Warning! -//! -//! We **do not** recommend you opt-out of the provided allocator for production contract -//! deployments! -//! -//! If you don't handle allocations correctly you can introduce security vulnerabilities -//! to your contracts. -//! -//! You may also introduce performance issues. This is because the code of your allocator -//! will be included in the final contract binary, potentially increasing gas usage -//! significantly. -//! -//! ## Why Change the Allocator? -//! -//! The default memory allocator was designed to have a tiny size footprint, and made some -//! compromises to achieve that, e.g it does not free/deallocate memory. -//! -//! You may have a use case where you want to deallocate memory, or allocate it using a -//! different strategy. -//! -//! Providing your own allocator lets you choose the right tradeoffs for your use case. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -// Here we set `dlmalloc` to be the global memory allocator. -// -// The [`GlobalAlloc`](https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html) trait is -// important to understand if you're swapping our your allocator. -#[cfg(not(feature = "std"))] -#[global_allocator] -static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; - -#[ink::contract] -mod custom_allocator { - use ink::prelude::{ - vec, - vec::Vec, - }; - - #[ink(storage)] - pub struct CustomAllocator { - /// Stores a single `bool` value on the storage. - /// - /// # Note - /// - /// We're using a `Vec` here as it allocates its elements onto the heap, as - /// opposed to the stack. This allows us to demonstrate that our new - /// allocator actually works. - value: Vec, - } - - impl CustomAllocator { - /// Constructor that initializes the `bool` value to the given `init_value`. - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { - value: vec![init_value], - } - } - - /// Creates a new flipper smart contract initialized to `false`. - #[ink(constructor)] - pub fn default() -> Self { - Self::new(Default::default()) - } - - /// A message that can be called on instantiated contracts. - /// This one flips the value of the stored `bool` from `true` - /// to `false` and vice versa. - #[ink(message)] - pub fn flip(&mut self) { - self.value[0] = !self.value[0]; - } - - /// Simply returns the current value of our `bool`. - #[ink(message)] - pub fn get(&self) -> bool { - self.value[0] - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - let custom_allocator = CustomAllocator::default(); - assert!(!custom_allocator.get()); - } - - #[ink::test] - fn it_works() { - let mut custom_allocator = CustomAllocator::new(false); - assert!(!custom_allocator.get()); - custom_allocator.flip(); - assert!(custom_allocator.get()); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - /// We test that we can upload and instantiate the contract using its default - /// constructor. - #[ink_e2e::test] - async fn default_works(mut client: Client) -> E2EResult<()> { - // Given - let mut constructor = CustomAllocatorRef::default(); - - // When - let contract = client - .instantiate("custom_allocator", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - // Then - let get = call.get(); - let get_result = client.call(&ink_e2e::alice(), &get).dry_run().await?; - assert!(matches!(get_result.return_value(), false)); - - Ok(()) - } - - /// We test that we can read and write a value from the on-chain contract - /// contract. - #[ink_e2e::test] - async fn it_works(mut client: Client) -> E2EResult<()> { - // Given - let mut constructor = CustomAllocatorRef::new(false); - let contract = client - .instantiate("custom_allocator", &ink_e2e::bob(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let get = call.get(); - let get_result = client.call(&ink_e2e::bob(), &get).dry_run().await?; - assert!(matches!(get_result.return_value(), false)); - - // When - let flip = call.flip(); - let _flip_result = client - .call(&ink_e2e::bob(), &flip) - .submit() - .await - .expect("flip failed"); - - // Then - let get = call.get(); - let get_result = client.call(&ink_e2e::bob(), &get).dry_run().await?; - assert!(matches!(get_result.return_value(), true)); - - Ok(()) - } - } -} diff --git a/integration-tests/custom-environment/.gitignore b/integration-tests/custom-environment/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/custom-environment/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/custom-environment/Cargo.toml b/integration-tests/custom-environment/Cargo.toml deleted file mode 100644 index d38008ea1e3..00000000000 --- a/integration-tests/custom-environment/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "custom-environment" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] - -# Assumes that the node used in E2E testing allows for at least 6 event topics. -permissive-node = [] diff --git a/integration-tests/custom-environment/README.md b/integration-tests/custom-environment/README.md deleted file mode 100644 index 8bd35b4614e..00000000000 --- a/integration-tests/custom-environment/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# `custom-environment` example - -## What is this example about? - -It demonstrates how to use custom environment, both in the contract and in the E2E tests. - -## Chain-side configuration - -To integrate this example into Substrate you need to adjust pallet contracts configuration in your runtime: - -```rust -// In your node's runtime configuration file (runtime.rs) -parameter_types! { - pub Schedule: pallet_contracts::Schedule = pallet_contracts::Schedule:: { - limits: pallet_contracts::Limits { - event_topics: 6, - ..Default::default() - }, - ..Default::default() - }; -} - -impl pallet_contracts::Config for Runtime { - … - type Schedule = Schedule; - … -} - ``` diff --git a/integration-tests/custom-environment/lib.rs b/integration-tests/custom-environment/lib.rs deleted file mode 100644 index 62b5d4e1f9a..00000000000 --- a/integration-tests/custom-environment/lib.rs +++ /dev/null @@ -1,146 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use ink::env::{DefaultEnvironment, Environment}; - -/// Our custom environment diverges from the `DefaultEnvironment` in the event topics -/// limit. -#[derive(Debug, Clone, PartialEq, Eq)] -#[ink::scale_derive(TypeInfo)] -pub enum EnvironmentWithManyTopics {} - -impl Environment for EnvironmentWithManyTopics { - // We allow for 5 topics in the event, therefore the contract pallet's schedule must - // allow for 6 of them (to allow the implicit topic for the event signature). - const MAX_EVENT_TOPICS: usize = - ::MAX_EVENT_TOPICS + 1; - - type AccountId = ::AccountId; - type Balance = ::Balance; - type Hash = ::Hash; - type BlockNumber = ::BlockNumber; - type Timestamp = ::Timestamp; - - type ChainExtension = ::ChainExtension; -} - -#[ink::contract(env = crate::EnvironmentWithManyTopics)] -mod runtime_call { - /// Trivial contract with a single message that emits an event with many topics. - #[ink(storage)] - #[derive(Default)] - pub struct Topics; - - /// An event that would be forbidden in the default environment, but is completely - /// valid in our custom one. - #[ink(event)] - #[derive(Default)] - pub struct EventWithTopics { - #[ink(topic)] - first_topic: Balance, - #[ink(topic)] - second_topic: Balance, - #[ink(topic)] - third_topic: Balance, - #[ink(topic)] - fourth_topic: Balance, - } - - impl Topics { - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - /// Emit an event with many topics. - #[ink(message)] - pub fn trigger(&mut self) { - self.env().emit_event(EventWithTopics::default()); - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn emits_event_with_many_topics() { - let mut contract = Topics::new(); - contract.trigger(); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 1); - - let emitted_event = ::decode( - &mut &emitted_events[0].data[..], - ); - - assert!(emitted_event.is_ok()); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = Result>; - - #[cfg(feature = "permissive-node")] - #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)] - async fn calling_custom_environment_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = TopicsRef::new(); - let contract = client - .instantiate("custom-environment", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let message = call.trigger(); - - let call_res = client - .call(&ink_e2e::alice(), &message) - .submit() - .await - .expect("call failed"); - - // then - call_res.contains_event("Contracts", "ContractEmitted"); - - Ok(()) - } - - #[cfg(not(feature = "permissive-node"))] - #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)] - async fn calling_custom_environment_fails_if_incompatible_with_node< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = TopicsRef::new(); - let contract = client - .instantiate("custom-environment", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let message = call.trigger(); - - // when - let call_res = client - .call(&ink_e2e::alice(), &message).dry_run() - .await; - - // then - assert!(call_res.is_err()); - - Ok(()) - } - } -} diff --git a/integration-tests/dns/.gitignore b/integration-tests/dns/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/dns/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/dns/Cargo.toml b/integration-tests/dns/Cargo.toml deleted file mode 100644 index 007fd9203b6..00000000000 --- a/integration-tests/dns/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "dns" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/dns/lib.rs b/integration-tests/dns/lib.rs deleted file mode 100644 index a46cdb6e9ea..00000000000 --- a/integration-tests/dns/lib.rs +++ /dev/null @@ -1,262 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod dns { - use ink::storage::Mapping; - - /// Emitted whenever a new name is being registered. - #[ink(event)] - pub struct Register { - #[ink(topic)] - name: Hash, - #[ink(topic)] - from: AccountId, - } - - /// Emitted whenever an address changes. - #[ink(event)] - pub struct SetAddress { - #[ink(topic)] - name: Hash, - from: AccountId, - #[ink(topic)] - old_address: Option, - #[ink(topic)] - new_address: AccountId, - } - - /// Emitted whenever a name is being transferred. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - name: Hash, - from: AccountId, - #[ink(topic)] - old_owner: Option, - #[ink(topic)] - new_owner: AccountId, - } - - /// Domain name service contract inspired by - /// [this blog post](https://medium.com/@chainx_org/secure-and-decentralized-polkadot-domain-name-system-e06c35c2a48d). - /// - /// # Note - /// - /// This is a port from the blog post's ink! 1.0 based version of the contract - /// to ink! 2.0. - /// - /// # Description - /// - /// The main function of this contract is domain name resolution which - /// refers to the retrieval of numeric values corresponding to readable - /// and easily memorable names such as "polka.dot" which can be used - /// to facilitate transfers, voting and DApp-related operations instead - /// of resorting to long IP addresses that are hard to remember. - #[ink(storage)] - pub struct DomainNameService { - /// A hashmap to store all name to addresses mapping. - name_to_address: Mapping, - /// A hashmap to store all name to owners mapping. - name_to_owner: Mapping, - /// The default address. - default_address: AccountId, - } - - impl Default for DomainNameService { - fn default() -> Self { - let mut name_to_address = Mapping::new(); - name_to_address.insert(Hash::default(), &zero_address()); - let mut name_to_owner = Mapping::new(); - name_to_owner.insert(Hash::default(), &zero_address()); - - Self { - name_to_address, - name_to_owner, - default_address: zero_address(), - } - } - } - - /// Errors that can occur upon calling this contract. - #[derive(Debug, PartialEq, Eq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Error { - /// Returned if the name already exists upon registration. - NameAlreadyExists, - /// Returned if caller is not owner while required to. - CallerIsNotOwner, - } - - /// Type alias for the contract's result type. - pub type Result = core::result::Result; - - impl DomainNameService { - /// Creates a new domain name service contract. - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - /// Register specific name with caller as owner. - #[ink(message)] - pub fn register(&mut self, name: Hash) -> Result<()> { - let caller = self.env().caller(); - if self.name_to_owner.contains(name) { - return Err(Error::NameAlreadyExists) - } - - self.name_to_owner.insert(name, &caller); - self.env().emit_event(Register { name, from: caller }); - - Ok(()) - } - - /// Set address for specific name. - #[ink(message)] - pub fn set_address(&mut self, name: Hash, new_address: AccountId) -> Result<()> { - let caller = self.env().caller(); - let owner = self.get_owner_or_default(name); - if caller != owner { - return Err(Error::CallerIsNotOwner) - } - - let old_address = self.name_to_address.get(name); - self.name_to_address.insert(name, &new_address); - - self.env().emit_event(SetAddress { - name, - from: caller, - old_address, - new_address, - }); - Ok(()) - } - - /// Transfer owner to another address. - #[ink(message)] - pub fn transfer(&mut self, name: Hash, to: AccountId) -> Result<()> { - let caller = self.env().caller(); - let owner = self.get_owner_or_default(name); - if caller != owner { - return Err(Error::CallerIsNotOwner) - } - - let old_owner = self.name_to_owner.get(name); - self.name_to_owner.insert(name, &to); - - self.env().emit_event(Transfer { - name, - from: caller, - old_owner, - new_owner: to, - }); - - Ok(()) - } - - /// Get address for specific name. - #[ink(message)] - pub fn get_address(&self, name: Hash) -> AccountId { - self.get_address_or_default(name) - } - - /// Get owner of specific name. - #[ink(message)] - pub fn get_owner(&self, name: Hash) -> AccountId { - self.get_owner_or_default(name) - } - - /// Returns the owner given the hash or the default address. - fn get_owner_or_default(&self, name: Hash) -> AccountId { - self.name_to_owner.get(name).unwrap_or(self.default_address) - } - - /// Returns the address given the hash or the default address. - fn get_address_or_default(&self, name: Hash) -> AccountId { - self.name_to_address - .get(name) - .unwrap_or(self.default_address) - } - } - - /// Helper for referencing the zero address (`0x00`). Note that in practice this - /// address should not be treated in any special way (such as a default - /// placeholder) since it has a known private key. - fn zero_address() -> AccountId { - [0u8; 32].into() - } - - #[cfg(test)] - mod tests { - use super::*; - - fn default_accounts( - ) -> ink::env::test::DefaultAccounts { - ink::env::test::default_accounts::() - } - - fn set_next_caller(caller: AccountId) { - ink::env::test::set_caller::(caller); - } - - #[ink::test] - fn register_works() { - let default_accounts = default_accounts(); - let name = Hash::from([0x99; 32]); - - set_next_caller(default_accounts.alice); - let mut contract = DomainNameService::new(); - - assert_eq!(contract.register(name), Ok(())); - assert_eq!(contract.register(name), Err(Error::NameAlreadyExists)); - } - - #[ink::test] - fn set_address_works() { - let accounts = default_accounts(); - let name = Hash::from([0x99; 32]); - - set_next_caller(accounts.alice); - - let mut contract = DomainNameService::new(); - assert_eq!(contract.register(name), Ok(())); - - // Caller is not owner, `set_address` should fail. - set_next_caller(accounts.bob); - assert_eq!( - contract.set_address(name, accounts.bob), - Err(Error::CallerIsNotOwner) - ); - - // Caller is owner, set_address will be successful - set_next_caller(accounts.alice); - assert_eq!(contract.set_address(name, accounts.bob), Ok(())); - assert_eq!(contract.get_address(name), accounts.bob); - } - - #[ink::test] - fn transfer_works() { - let accounts = default_accounts(); - let name = Hash::from([0x99; 32]); - - set_next_caller(accounts.alice); - - let mut contract = DomainNameService::new(); - assert_eq!(contract.register(name), Ok(())); - - // Test transfer of owner. - assert_eq!(contract.transfer(name, accounts.bob), Ok(())); - - // Owner is bob, alice `set_address` should fail. - assert_eq!( - contract.set_address(name, accounts.bob), - Err(Error::CallerIsNotOwner) - ); - - set_next_caller(accounts.bob); - // Now owner is bob, `set_address` should be successful. - assert_eq!(contract.set_address(name, accounts.bob), Ok(())); - assert_eq!(contract.get_address(name), accounts.bob); - } - } -} diff --git a/integration-tests/e2e-call-runtime/.gitignore b/integration-tests/e2e-call-runtime/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/e2e-call-runtime/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/e2e-call-runtime/Cargo.toml b/integration-tests/e2e-call-runtime/Cargo.toml deleted file mode 100644 index d376a169a55..00000000000 --- a/integration-tests/e2e-call-runtime/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "e2e_call_runtime" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/e2e-call-runtime/lib.rs b/integration-tests/e2e-call-runtime/lib.rs deleted file mode 100644 index b3b5ffe7926..00000000000 --- a/integration-tests/e2e-call-runtime/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -pub mod e2e_call_runtime { - #[ink(storage)] - #[derive(Default)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn new() -> Self { - Self {} - } - - #[ink(message)] - pub fn get_contract_balance(&self) -> Balance { - self.env().balance() - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - subxt::dynamic::Value, - ChainBackend, - ContractsBackend, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn call_runtime_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = ContractRef::new(); - let contract = client - .instantiate("e2e_call_runtime", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - let transfer_amount = 100_000_000_000u128; - - // when - let call_data = vec![ - // A value representing a `MultiAddress`. We want the - // "Id" variant, and that will ultimately contain the - // bytes for our destination address - Value::unnamed_variant("Id", [Value::from_bytes(&contract.account_id)]), - // A value representing the amount we'd like to transfer. - Value::u128(transfer_amount), - ]; - - let get_balance = call.get_contract_balance(); - let pre_balance = client - .call(&ink_e2e::alice(), &get_balance) - .dry_run() - .await? - .return_value(); - - // Send funds from Alice to the contract using Balances::transfer - client - .runtime_call( - &ink_e2e::alice(), - "Balances", - "transfer_allow_death", - call_data, - ) - .await - .expect("runtime call failed"); - - // then - let get_balance = call.get_contract_balance(); - let get_balance_res = client - .call(&ink_e2e::alice(), &get_balance) - .dry_run() - .await?; - - assert_eq!( - get_balance_res.return_value(), - pre_balance + transfer_amount - ); - - Ok(()) - } - } -} diff --git a/integration-tests/e2e-runtime-only-backend/.gitignore b/integration-tests/e2e-runtime-only-backend/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/e2e-runtime-only-backend/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/e2e-runtime-only-backend/Cargo.toml b/integration-tests/e2e-runtime-only-backend/Cargo.toml deleted file mode 100644 index 34b769e03eb..00000000000 --- a/integration-tests/e2e-runtime-only-backend/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "e2e-runtime-only-backend" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e", features = ["drink"] } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/e2e-runtime-only-backend/lib.rs b/integration-tests/e2e-runtime-only-backend/lib.rs deleted file mode 100644 index 9f3a66d350e..00000000000 --- a/integration-tests/e2e-runtime-only-backend/lib.rs +++ /dev/null @@ -1,159 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -pub mod flipper { - #[ink(storage)] - pub struct Flipper { - value: bool, - } - - impl Flipper { - /// Creates a new flipper smart contract initialized with the given value. - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { value: init_value } - } - - /// Creates a new flipper smart contract initialized to `false`. - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - /// Flips the current value of the Flipper's boolean. - #[ink(message)] - pub fn flip(&mut self) { - self.value = !self.value; - } - - /// Returns the current value of the Flipper's boolean. - #[ink(message)] - pub fn get(&self) -> bool { - self.value - } - - /// Returns the current balance of the Flipper. - #[ink(message)] - pub fn get_contract_balance(&self) -> Balance { - self.env().balance() - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - subxt::dynamic::Value, - ChainBackend, - ContractsBackend, - }; - - type E2EResult = std::result::Result>; - - /// Tests standard flipper scenario: - /// - deploy the flipper contract with initial value `false` - /// - flip the flipper - /// - get the flipper's value - /// - assert that the value is `true` - #[ink_e2e::test(backend(runtime_only()))] - async fn it_works(mut client: Client) -> E2EResult<()> { - // given - const INITIAL_VALUE: bool = false; - let mut constructor = FlipperRef::new(INITIAL_VALUE); - - let contract = client - .instantiate( - "e2e-runtime-only-backend", - &ink_e2e::alice(), - &mut constructor, - ) - .submit() - .await - .expect("deploy failed"); - - // when - let mut call = contract.call::(); - let _flip_res = client.call(&ink_e2e::bob(), &call.flip()).submit().await; - - // then - let get_res = client.call(&ink_e2e::bob(), &call.get()).dry_run().await?; - assert_eq!(get_res.return_value(), !INITIAL_VALUE); - - Ok(()) - } - - /// Tests runtime call scenario: - /// - deploy the flipper contract - /// - get the contract's balance - /// - transfer some funds to the contract using runtime call - /// - get the contract's balance again - /// - assert that the contract's balance increased by the transferred amount - #[ink_e2e::test(backend(runtime_only()))] - async fn runtime_call_works() -> E2EResult<()> { - // given - let mut constructor = FlipperRef::new(false); - - let contract = client - .instantiate( - "e2e-runtime-only-backend", - &ink_e2e::alice(), - &mut constructor, - ) - .submit() - .await - .expect("deploy failed"); - let call = contract.call::(); - - let old_balance = client - .call(&ink_e2e::alice(), &call.get_contract_balance()) - .submit() - .await - .expect("get_contract_balance failed") - .return_value(); - - const ENDOWMENT: u128 = 10; - - // when - let call_data = vec![ - Value::from_bytes(&contract.account_id), - Value::u128(ENDOWMENT), - ]; - client - .runtime_call( - &ink_e2e::alice(), - "Balances", - "transfer_allow_death", - call_data, - ) - .await - .expect("runtime call failed"); - - // then - let new_balance = client - .call(&ink_e2e::alice(), &call.get_contract_balance()) - .submit() - .await - .expect("get_contract_balance failed") - .return_value(); - - assert_eq!(old_balance + ENDOWMENT, new_balance); - Ok(()) - } - - /// Just instantiate a contract using non-default runtime. - #[ink_e2e::test(backend(runtime_only(runtime = ink_e2e::MinimalRuntime)))] - async fn custom_runtime(mut client: Client) -> E2EResult<()> { - client - .instantiate( - "e2e-runtime-only-backend", - &ink_e2e::alice(), - &mut FlipperRef::new(false), - ) - .submit() - .await - .expect("instantiate failed"); - - Ok(()) - } - } -} diff --git a/integration-tests/erc1155/.gitignore b/integration-tests/erc1155/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/erc1155/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/erc1155/Cargo.toml b/integration-tests/erc1155/Cargo.toml deleted file mode 100644 index 5f3e2496f90..00000000000 --- a/integration-tests/erc1155/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "erc1155" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/erc1155/lib.rs b/integration-tests/erc1155/lib.rs deleted file mode 100644 index ba06c1ea052..00000000000 --- a/integration-tests/erc1155/lib.rs +++ /dev/null @@ -1,805 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use ink::{ - prelude::vec::Vec, - primitives::AccountId, -}; - -// This is the return value that we expect if a smart contract supports receiving ERC-1155 -// tokens. -// -// It is calculated with -// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`, and -// corresponds to 0xf23a6e61. -#[cfg_attr(test, allow(dead_code))] -const ON_ERC_1155_RECEIVED_SELECTOR: [u8; 4] = [0xF2, 0x3A, 0x6E, 0x61]; - -// This is the return value that we expect if a smart contract supports batch receiving -// ERC-1155 tokens. -// -// It is calculated with -// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)" -// ))`, and corresponds to 0xbc197c81. -const _ON_ERC_1155_BATCH_RECEIVED_SELECTOR: [u8; 4] = [0xBC, 0x19, 0x7C, 0x81]; - -/// A type representing the unique IDs of tokens managed by this contract. -pub type TokenId = u128; - -type Balance = ::Balance; - -// The ERC-1155 error types. -#[derive(Debug, PartialEq, Eq)] -#[ink::scale_derive(Encode, Decode, TypeInfo)] -pub enum Error { - /// This token ID has not yet been created by the contract. - UnexistentToken, - /// The caller tried to sending tokens to the zero-address (`0x00`). - ZeroAddressTransfer, - /// The caller is not approved to transfer tokens on behalf of the account. - NotApproved, - /// The account does not have enough funds to complete the transfer. - InsufficientBalance, - /// An account does not need to approve themselves to transfer tokens. - SelfApproval, - /// The number of tokens being transferred does not match the specified number of - /// transfers. - BatchTransferMismatch, -} - -// The ERC-1155 result types. -pub type Result = core::result::Result; - -/// Evaluate `$x:expr` and if not true return `Err($y:expr)`. -/// -/// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`. -macro_rules! ensure { - ( $condition:expr, $error:expr $(,)? ) => {{ - if !$condition { - return ::core::result::Result::Err(::core::convert::Into::into($error)) - } - }}; -} - -/// The interface for an ERC-1155 compliant contract. -/// -/// The interface is defined here: . -/// -/// The goal of ERC-1155 is to allow a single contract to manage a variety of assets. -/// These assets can be fungible, non-fungible, or a combination. -/// -/// By tracking multiple assets the ERC-1155 standard is able to support batch transfers, -/// which make it easy to transfer a mix of multiple tokens at once. -#[ink::trait_definition] -pub trait Erc1155 { - /// Transfer a `value` amount of `token_id` tokens to the `to` account from the `from` - /// account. - /// - /// Note that the call does not have to originate from the `from` account, and may - /// originate from any account which is approved to transfer `from`'s tokens. - #[ink(message)] - fn safe_transfer_from( - &mut self, - from: AccountId, - to: AccountId, - token_id: TokenId, - value: Balance, - data: Vec, - ) -> Result<()>; - - /// Perform a batch transfer of `token_ids` to the `to` account from the `from` - /// account. - /// - /// The number of `values` specified to be transferred must match the number of - /// `token_ids`, otherwise this call will revert. - /// - /// Note that the call does not have to originate from the `from` account, and may - /// originate from any account which is approved to transfer `from`'s tokens. - #[ink(message)] - fn safe_batch_transfer_from( - &mut self, - from: AccountId, - to: AccountId, - token_ids: Vec, - values: Vec, - data: Vec, - ) -> Result<()>; - - /// Query the balance of a specific token for the provided account. - #[ink(message)] - fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance; - - /// Query the balances for a set of tokens for a set of accounts. - /// - /// E.g use this call if you want to query what Alice and Bob's balances are for - /// Tokens ID 1 and ID 2. - /// - /// This will return all the balances for a given owner before moving on to the next - /// owner. In the example above this means that the return value should look like: - /// - /// [Alice Balance of Token ID 1, Alice Balance of Token ID 2, Bob Balance of Token ID - /// 1, Bob Balance of Token ID 2] - #[ink(message)] - fn balance_of_batch( - &self, - owners: Vec, - token_ids: Vec, - ) -> Vec; - - /// Enable or disable a third party, known as an `operator`, to control all tokens on - /// behalf of the caller. - #[ink(message)] - fn set_approval_for_all(&mut self, operator: AccountId, approved: bool) - -> Result<()>; - - /// Query if the given `operator` is allowed to control all of `owner`'s tokens. - #[ink(message)] - fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool; -} - -/// The interface for an ERC-1155 Token Receiver contract. -/// -/// The interface is defined here: . -/// -/// Smart contracts which want to accept token transfers must implement this interface. By -/// default if a contract does not support this interface any transactions originating -/// from an ERC-1155 compliant contract which attempt to transfer tokens directly to the -/// contract's address must be reverted. -#[ink::trait_definition] -pub trait Erc1155TokenReceiver { - /// Handle the receipt of a single ERC-1155 token. - /// - /// This should be called by a compliant ERC-1155 contract if the intended recipient - /// is a smart contract. - /// - /// If the smart contract implementing this interface accepts token transfers then it - /// must return `ON_ERC_1155_RECEIVED_SELECTOR` from this function. To reject a - /// transfer it must revert. - /// - /// Any callers must revert if they receive anything other than - /// `ON_ERC_1155_RECEIVED_SELECTOR` as a return value. - #[ink(message, selector = 0xF23A6E61)] - fn on_received( - &mut self, - operator: AccountId, - from: AccountId, - token_id: TokenId, - value: Balance, - data: Vec, - ) -> Vec; - - /// Handle the receipt of multiple ERC-1155 tokens. - /// - /// This should be called by a compliant ERC-1155 contract if the intended recipient - /// is a smart contract. - /// - /// If the smart contract implementing this interface accepts token transfers then it - /// must return `BATCH_ON_ERC_1155_RECEIVED_SELECTOR` from this function. To - /// reject a transfer it must revert. - /// - /// Any callers must revert if they receive anything other than - /// `BATCH_ON_ERC_1155_RECEIVED_SELECTOR` as a return value. - #[ink(message, selector = 0xBC197C81)] - fn on_batch_received( - &mut self, - operator: AccountId, - from: AccountId, - token_ids: Vec, - values: Vec, - data: Vec, - ) -> Vec; -} - -#[ink::contract] -mod erc1155 { - use super::*; - - use ink::storage::Mapping; - - type Owner = AccountId; - type Operator = AccountId; - - /// Indicate that a token transfer has occured. - /// - /// This must be emitted even if a zero value transfer occurs. - #[ink(event)] - pub struct TransferSingle { - #[ink(topic)] - operator: Option, - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - token_id: TokenId, - value: Balance, - } - - /// Indicate that an approval event has happened. - #[ink(event)] - pub struct ApprovalForAll { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - operator: AccountId, - approved: bool, - } - - /// Indicate that a token's URI has been updated. - #[ink(event)] - pub struct Uri { - value: ink::prelude::string::String, - #[ink(topic)] - token_id: TokenId, - } - - /// An ERC-1155 contract. - #[ink(storage)] - #[derive(Default)] - pub struct Contract { - /// Tracks the balances of accounts across the different tokens that they might - /// be holding. - balances: Mapping<(AccountId, TokenId), Balance>, - /// Which accounts (called operators) have been approved to spend funds on behalf - /// of an owner. - approvals: Mapping<(Owner, Operator), ()>, - /// A unique identifier for the tokens which have been minted (and are therefore - /// supported) by this contract. - token_id_nonce: TokenId, - } - - impl Contract { - /// Initialize a default instance of this ERC-1155 implementation. - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - /// Create the initial supply for a token. - /// - /// The initial supply will be provided to the caller (a.k.a the minter), and the - /// `token_id` will be assigned by the smart contract. - /// - /// Note that as implemented anyone can create tokens. If you were to instantiate - /// this contract in a production environment you'd probably want to lock down - /// the addresses that are allowed to create tokens. - #[ink(message)] - pub fn create(&mut self, value: Balance) -> TokenId { - let caller = self.env().caller(); - - // Given that TokenId is a `u128` the likelihood of this overflowing is pretty - // slim. - #[allow(clippy::arithmetic_side_effects)] - { - self.token_id_nonce += 1; - } - self.balances.insert((caller, self.token_id_nonce), &value); - - // Emit transfer event but with mint semantics - self.env().emit_event(TransferSingle { - operator: Some(caller), - from: None, - to: if value == 0 { None } else { Some(caller) }, - token_id: self.token_id_nonce, - value, - }); - - self.token_id_nonce - } - - /// Mint a `value` amount of `token_id` tokens. - /// - /// It is assumed that the token has already been `create`-ed. The newly minted - /// supply will be assigned to the caller (a.k.a the minter). - /// - /// Note that as implemented anyone can mint tokens. If you were to instantiate - /// this contract in a production environment you'd probably want to lock down - /// the addresses that are allowed to mint tokens. - #[ink(message)] - pub fn mint(&mut self, token_id: TokenId, value: Balance) -> Result<()> { - ensure!(token_id <= self.token_id_nonce, Error::UnexistentToken); - - let caller = self.env().caller(); - self.balances.insert((caller, token_id), &value); - - // Emit transfer event but with mint semantics - self.env().emit_event(TransferSingle { - operator: Some(caller), - from: None, - to: Some(caller), - token_id, - value, - }); - - Ok(()) - } - - // Helper function for performing single token transfers. - // - // Should not be used directly since it's missing certain checks which are - // important to the ERC-1155 standard (it is expected that the caller has - // already performed these). - // - // # Panics - // - // If `from` does not hold any `token_id` tokens. - fn perform_transfer( - &mut self, - from: AccountId, - to: AccountId, - token_id: TokenId, - value: Balance, - ) { - let mut sender_balance = self - .balances - .get((from, token_id)) - .expect("Caller should have ensured that `from` holds `token_id`."); - // checks that sender_balance >= value were performed by caller - #[allow(clippy::arithmetic_side_effects)] - { - sender_balance -= value; - } - self.balances.insert((from, token_id), &sender_balance); - - let mut recipient_balance = self.balances.get((to, token_id)).unwrap_or(0); - recipient_balance = recipient_balance.checked_add(value).unwrap(); - self.balances.insert((to, token_id), &recipient_balance); - - let caller = self.env().caller(); - self.env().emit_event(TransferSingle { - operator: Some(caller), - from: Some(from), - to: Some(to), - token_id, - value, - }); - } - - // Check if the address at `to` is a smart contract which accepts ERC-1155 token - // transfers. - // - // If they're a smart contract which **doesn't** accept tokens transfers this call - // will revert. Otherwise we risk locking user funds at in that contract - // with no chance of recovery. - #[cfg_attr(test, allow(unused_variables))] - fn transfer_acceptance_check( - &mut self, - caller: AccountId, - from: AccountId, - to: AccountId, - token_id: TokenId, - value: Balance, - data: Vec, - ) { - // This is disabled during tests due to the use of `invoke_contract()` not - // being supported (tests end up panicking). - #[cfg(not(test))] - { - use ink::env::call::{ - build_call, - ExecutionInput, - Selector, - }; - - // If our recipient is a smart contract we need to see if they accept or - // reject this transfer. If they reject it we need to revert the call. - let result = build_call::() - .call(to) - .gas_limit(5000) - .exec_input( - ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR)) - .push_arg(caller) - .push_arg(from) - .push_arg(token_id) - .push_arg(value) - .push_arg(data), - ) - .returns::>() - .params() - .try_invoke(); - - match result { - Ok(v) => { - ink::env::debug_println!( - "Received return value \"{:?}\" from contract {:?}", - v.clone().expect( - "Call should be valid, don't expect a `LangError`." - ), - from - ); - assert_eq!( - v.clone().expect("Call should be valid, don't expect a `LangError`."), - &ON_ERC_1155_RECEIVED_SELECTOR[..], - "The recipient contract at {to:?} does not accept token transfers.\n - Expected: {ON_ERC_1155_RECEIVED_SELECTOR:?}, Got {v:?}" - ) - } - Err(e) => { - use ink::env::ReturnErrorCode; - - match e { - ink::env::Error::ReturnError( - ReturnErrorCode::CodeNotFound - | ReturnErrorCode::NotCallable, - ) => { - // Our recipient wasn't a smart contract, so there's - // nothing more for - // us to do - ink::env::debug_println!("Recipient at {:?} from is not a smart contract ({:?})", from, e); - } - _ => { - // We got some sort of error from the call to our - // recipient smart - // contract, and as such we must revert this call - panic!( - "Got error \"{e:?}\" while trying to call {from:?}" - ) - } - } - } - } - } - } - } - - impl super::Erc1155 for Contract { - #[ink(message)] - fn safe_transfer_from( - &mut self, - from: AccountId, - to: AccountId, - token_id: TokenId, - value: Balance, - data: Vec, - ) -> Result<()> { - let caller = self.env().caller(); - if caller != from { - ensure!(self.is_approved_for_all(from, caller), Error::NotApproved); - } - - ensure!(to != zero_address(), Error::ZeroAddressTransfer); - - let balance = self.balance_of(from, token_id); - ensure!(balance >= value, Error::InsufficientBalance); - - self.perform_transfer(from, to, token_id, value); - self.transfer_acceptance_check(caller, from, to, token_id, value, data); - - Ok(()) - } - - #[ink(message)] - fn safe_batch_transfer_from( - &mut self, - from: AccountId, - to: AccountId, - token_ids: Vec, - values: Vec, - data: Vec, - ) -> Result<()> { - let caller = self.env().caller(); - if caller != from { - ensure!(self.is_approved_for_all(from, caller), Error::NotApproved); - } - - ensure!(to != zero_address(), Error::ZeroAddressTransfer); - ensure!(!token_ids.is_empty(), Error::BatchTransferMismatch); - ensure!( - token_ids.len() == values.len(), - Error::BatchTransferMismatch, - ); - - let transfers = token_ids.iter().zip(values.iter()); - for (&id, &v) in transfers.clone() { - let balance = self.balance_of(from, id); - ensure!(balance >= v, Error::InsufficientBalance); - } - - for (&id, &v) in transfers { - self.perform_transfer(from, to, id, v); - } - - // Can use the any token ID/value here, we really just care about knowing if - // `to` is a smart contract which accepts transfers - self.transfer_acceptance_check( - caller, - from, - to, - token_ids[0], - values[0], - data, - ); - - Ok(()) - } - - #[ink(message)] - fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance { - self.balances.get((owner, token_id)).unwrap_or(0) - } - - #[ink(message)] - fn balance_of_batch( - &self, - owners: Vec, - token_ids: Vec, - ) -> Vec { - let mut output = Vec::new(); - for o in &owners { - for t in &token_ids { - let amount = self.balance_of(*o, *t); - output.push(amount); - } - } - output - } - - #[ink(message)] - fn set_approval_for_all( - &mut self, - operator: AccountId, - approved: bool, - ) -> Result<()> { - let caller = self.env().caller(); - ensure!(operator != caller, Error::SelfApproval); - - if approved { - self.approvals.insert((&caller, &operator), &()); - } else { - self.approvals.remove((&caller, &operator)); - } - - self.env().emit_event(ApprovalForAll { - owner: caller, - operator, - approved, - }); - - Ok(()) - } - - #[ink(message)] - fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { - self.approvals.contains((&owner, &operator)) - } - } - - impl super::Erc1155TokenReceiver for Contract { - #[ink(message, selector = 0xF23A6E61)] - fn on_received( - &mut self, - _operator: AccountId, - _from: AccountId, - _token_id: TokenId, - _value: Balance, - _data: Vec, - ) -> Vec { - // The ERC-1155 standard dictates that if a contract does not accept token - // transfers directly to the contract, then the contract must - // revert. - // - // This prevents a user from unintentionally transferring tokens to a smart - // contract and getting their funds stuck without any sort of - // recovery mechanism. - // - // Note that the choice of whether or not to accept tokens is implementation - // specific, and we've decided to not accept them in this - // implementation. - unimplemented!("This smart contract does not accept token transfer.") - } - - #[ink(message, selector = 0xBC197C81)] - fn on_batch_received( - &mut self, - _operator: AccountId, - _from: AccountId, - _token_ids: Vec, - _values: Vec, - _data: Vec, - ) -> Vec { - // The ERC-1155 standard dictates that if a contract does not accept token - // transfers directly to the contract, then the contract must - // revert. - // - // This prevents a user from unintentionally transferring tokens to a smart - // contract and getting their funds stuck without any sort of - // recovery mechanism. - // - // Note that the choice of whether or not to accept tokens is implementation - // specific, and we've decided to not accept them in this - // implementation. - unimplemented!("This smart contract does not accept batch token transfers.") - } - } - - /// Helper for referencing the zero address (`0x00`). Note that in practice this - /// address should not be treated in any special way (such as a default - /// placeholder) since it has a known private key. - fn zero_address() -> AccountId { - [0u8; 32].into() - } - - #[cfg(test)] - mod tests { - /// Imports all the definitions from the outer scope so we can use them here. - use super::*; - use crate::Erc1155; - - fn set_sender(sender: AccountId) { - ink::env::test::set_caller::(sender); - } - - fn default_accounts() -> ink::env::test::DefaultAccounts { - ink::env::test::default_accounts::() - } - - fn alice() -> AccountId { - default_accounts().alice - } - - fn bob() -> AccountId { - default_accounts().bob - } - - fn charlie() -> AccountId { - default_accounts().charlie - } - - fn init_contract() -> Contract { - let mut erc = Contract::new(); - erc.balances.insert((alice(), 1), &10); - erc.balances.insert((alice(), 2), &20); - erc.balances.insert((bob(), 1), &10); - - erc - } - - #[ink::test] - fn can_get_correct_balance_of() { - let erc = init_contract(); - - assert_eq!(erc.balance_of(alice(), 1), 10); - assert_eq!(erc.balance_of(alice(), 2), 20); - assert_eq!(erc.balance_of(alice(), 3), 0); - assert_eq!(erc.balance_of(bob(), 2), 0); - } - - #[ink::test] - fn can_get_correct_batch_balance_of() { - let erc = init_contract(); - - assert_eq!( - erc.balance_of_batch(vec![alice()], vec![1, 2, 3]), - vec![10, 20, 0] - ); - assert_eq!( - erc.balance_of_batch(vec![alice(), bob()], vec![1]), - vec![10, 10] - ); - - assert_eq!( - erc.balance_of_batch(vec![alice(), bob(), charlie()], vec![1, 2]), - vec![10, 20, 10, 0, 0, 0] - ); - } - - #[ink::test] - fn can_send_tokens_between_accounts() { - let mut erc = init_contract(); - - assert!(erc.safe_transfer_from(alice(), bob(), 1, 5, vec![]).is_ok()); - assert_eq!(erc.balance_of(alice(), 1), 5); - assert_eq!(erc.balance_of(bob(), 1), 15); - - assert!(erc.safe_transfer_from(alice(), bob(), 2, 5, vec![]).is_ok()); - assert_eq!(erc.balance_of(alice(), 2), 15); - assert_eq!(erc.balance_of(bob(), 2), 5); - } - - #[ink::test] - fn sending_too_many_tokens_fails() { - let mut erc = init_contract(); - let res = erc.safe_transfer_from(alice(), bob(), 1, 99, vec![]); - assert_eq!(res.unwrap_err(), Error::InsufficientBalance); - } - - #[ink::test] - fn sending_tokens_to_zero_address_fails() { - let burn: AccountId = [0; 32].into(); - - let mut erc = init_contract(); - let res = erc.safe_transfer_from(alice(), burn, 1, 10, vec![]); - assert_eq!(res.unwrap_err(), Error::ZeroAddressTransfer); - } - - #[ink::test] - fn can_send_batch_tokens() { - let mut erc = init_contract(); - assert!(erc - .safe_batch_transfer_from(alice(), bob(), vec![1, 2], vec![5, 10], vec![]) - .is_ok()); - - let balances = erc.balance_of_batch(vec![alice(), bob()], vec![1, 2]); - assert_eq!(balances, vec![5, 10, 15, 10]) - } - - #[ink::test] - fn rejects_batch_if_lengths_dont_match() { - let mut erc = init_contract(); - let res = erc.safe_batch_transfer_from( - alice(), - bob(), - vec![1, 2, 3], - vec![5], - vec![], - ); - assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch); - } - - #[ink::test] - fn batch_transfers_fail_if_len_is_zero() { - let mut erc = init_contract(); - let res = - erc.safe_batch_transfer_from(alice(), bob(), vec![], vec![], vec![]); - assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch); - } - - #[ink::test] - fn operator_can_send_tokens() { - let mut erc = init_contract(); - - let owner = alice(); - let operator = bob(); - - set_sender(owner); - assert!(erc.set_approval_for_all(operator, true).is_ok()); - - set_sender(operator); - assert!(erc - .safe_transfer_from(owner, charlie(), 1, 5, vec![]) - .is_ok()); - assert_eq!(erc.balance_of(alice(), 1), 5); - assert_eq!(erc.balance_of(charlie(), 1), 5); - } - - #[ink::test] - fn approvals_work() { - let mut erc = init_contract(); - let owner = alice(); - let operator = bob(); - let another_operator = charlie(); - - // Note: All of these tests are from the context of the owner who is either - // allowing or disallowing an operator to control their funds. - set_sender(owner); - assert!(!erc.is_approved_for_all(owner, operator)); - - assert!(erc.set_approval_for_all(operator, true).is_ok()); - assert!(erc.is_approved_for_all(owner, operator)); - - assert!(erc.set_approval_for_all(another_operator, true).is_ok()); - assert!(erc.is_approved_for_all(owner, another_operator)); - - assert!(erc.set_approval_for_all(operator, false).is_ok()); - assert!(!erc.is_approved_for_all(owner, operator)); - } - - #[ink::test] - fn minting_tokens_works() { - let mut erc = Contract::new(); - - set_sender(alice()); - assert_eq!(erc.create(0), 1); - assert_eq!(erc.balance_of(alice(), 1), 0); - - assert!(erc.mint(1, 123).is_ok()); - assert_eq!(erc.balance_of(alice(), 1), 123); - } - - #[ink::test] - fn minting_not_allowed_for_nonexistent_tokens() { - let mut erc = Contract::new(); - - let res = erc.mint(1, 123); - assert_eq!(res.unwrap_err(), Error::UnexistentToken); - } - } -} diff --git a/integration-tests/erc20/.gitignore b/integration-tests/erc20/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/erc20/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/erc20/Cargo.toml b/integration-tests/erc20/Cargo.toml deleted file mode 100644 index 0991702fd83..00000000000 --- a/integration-tests/erc20/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "erc20" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/erc20/lib.rs b/integration-tests/erc20/lib.rs deleted file mode 100644 index d0b0cdb7fda..00000000000 --- a/integration-tests/erc20/lib.rs +++ /dev/null @@ -1,643 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod erc20 { - use ink::storage::Mapping; - - /// A simple ERC-20 contract. - #[ink(storage)] - #[derive(Default)] - pub struct Erc20 { - /// Total token supply. - total_supply: Balance, - /// Mapping from owner to number of owned token. - balances: Mapping, - /// Mapping of the token amount which an account is allowed to withdraw - /// from another account. - allowances: Mapping<(AccountId, AccountId), Balance>, - } - - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - value: Balance, - } - - /// Event emitted when an approval occurs that `spender` is allowed to withdraw - /// up to the amount of `value` tokens from `owner`. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - spender: AccountId, - value: Balance, - } - - /// The ERC-20 error types. - #[derive(Debug, PartialEq, Eq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Error { - /// Returned if not enough balance to fulfill a request is available. - InsufficientBalance, - /// Returned if not enough allowance to fulfill a request is available. - InsufficientAllowance, - } - - /// The ERC-20 result type. - pub type Result = core::result::Result; - - impl Erc20 { - /// Creates a new ERC-20 contract with the specified initial supply. - #[ink(constructor)] - pub fn new(total_supply: Balance) -> Self { - let mut balances = Mapping::default(); - let caller = Self::env().caller(); - balances.insert(caller, &total_supply); - Self::env().emit_event(Transfer { - from: None, - to: Some(caller), - value: total_supply, - }); - Self { - total_supply, - balances, - allowances: Default::default(), - } - } - - /// Returns the total token supply. - #[ink(message)] - pub fn total_supply(&self) -> Balance { - self.total_supply - } - - /// Returns the account balance for the specified `owner`. - /// - /// Returns `0` if the account is non-existent. - #[ink(message)] - pub fn balance_of(&self, owner: AccountId) -> Balance { - self.balance_of_impl(&owner) - } - - /// Returns the account balance for the specified `owner`. - /// - /// Returns `0` if the account is non-existent. - /// - /// # Note - /// - /// Prefer to call this method over `balance_of` since this - /// works using references which are more efficient in Wasm. - #[inline] - fn balance_of_impl(&self, owner: &AccountId) -> Balance { - self.balances.get(owner).unwrap_or_default() - } - - /// Returns the amount which `spender` is still allowed to withdraw from `owner`. - /// - /// Returns `0` if no allowance has been set. - #[ink(message)] - pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { - self.allowance_impl(&owner, &spender) - } - - /// Returns the amount which `spender` is still allowed to withdraw from `owner`. - /// - /// Returns `0` if no allowance has been set. - /// - /// # Note - /// - /// Prefer to call this method over `allowance` since this - /// works using references which are more efficient in Wasm. - #[inline] - fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance { - self.allowances.get((owner, spender)).unwrap_or_default() - } - - /// Transfers `value` amount of tokens from the caller's account to account `to`. - /// - /// On success a `Transfer` event is emitted. - /// - /// # Errors - /// - /// Returns `InsufficientBalance` error if there are not enough tokens on - /// the caller's account balance. - #[ink(message)] - pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> { - let from = self.env().caller(); - self.transfer_from_to(&from, &to, value) - } - - /// Allows `spender` to withdraw from the caller's account multiple times, up to - /// the `value` amount. - /// - /// If this function is called again it overwrites the current allowance with - /// `value`. - /// - /// An `Approval` event is emitted. - #[ink(message)] - pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { - let owner = self.env().caller(); - self.allowances.insert((&owner, &spender), &value); - self.env().emit_event(Approval { - owner, - spender, - value, - }); - Ok(()) - } - - /// Transfers `value` tokens on the behalf of `from` to the account `to`. - /// - /// This can be used to allow a contract to transfer tokens on ones behalf and/or - /// to charge fees in sub-currencies, for example. - /// - /// On success a `Transfer` event is emitted. - /// - /// # Errors - /// - /// Returns `InsufficientAllowance` error if there are not enough tokens allowed - /// for the caller to withdraw from `from`. - /// - /// Returns `InsufficientBalance` error if there are not enough tokens on - /// the account balance of `from`. - #[ink(message)] - pub fn transfer_from( - &mut self, - from: AccountId, - to: AccountId, - value: Balance, - ) -> Result<()> { - let caller = self.env().caller(); - let allowance = self.allowance_impl(&from, &caller); - if allowance < value { - return Err(Error::InsufficientAllowance) - } - self.transfer_from_to(&from, &to, value)?; - // We checked that allowance >= value - #[allow(clippy::arithmetic_side_effects)] - self.allowances - .insert((&from, &caller), &(allowance - value)); - Ok(()) - } - - /// Transfers `value` amount of tokens from the caller's account to account `to`. - /// - /// On success a `Transfer` event is emitted. - /// - /// # Errors - /// - /// Returns `InsufficientBalance` error if there are not enough tokens on - /// the caller's account balance. - fn transfer_from_to( - &mut self, - from: &AccountId, - to: &AccountId, - value: Balance, - ) -> Result<()> { - let from_balance = self.balance_of_impl(from); - if from_balance < value { - return Err(Error::InsufficientBalance) - } - // We checked that from_balance >= value - #[allow(clippy::arithmetic_side_effects)] - self.balances.insert(from, &(from_balance - value)); - let to_balance = self.balance_of_impl(to); - self.balances - .insert(to, &(to_balance.checked_add(value).unwrap())); - self.env().emit_event(Transfer { - from: Some(*from), - to: Some(*to), - value, - }); - Ok(()) - } - } - - #[cfg(test)] - mod tests { - use super::*; - - use ink::primitives::{ - Clear, - Hash, - }; - - fn assert_transfer_event( - event: &ink::env::test::EmittedEvent, - expected_from: Option, - expected_to: Option, - expected_value: Balance, - ) { - let decoded_event = - ::decode(&mut &event.data[..]) - .expect("encountered invalid contract event data buffer"); - let Transfer { from, to, value } = decoded_event; - assert_eq!(from, expected_from, "encountered invalid Transfer.from"); - assert_eq!(to, expected_to, "encountered invalid Transfer.to"); - assert_eq!(value, expected_value, "encountered invalid Trasfer.value"); - - let mut expected_topics = Vec::new(); - expected_topics.push( - ink::blake2x256!("Transfer(Option,Option,Balance)") - .into(), - ); - if let Some(from) = expected_from { - expected_topics.push(encoded_into_hash(from)); - } else { - expected_topics.push(Hash::CLEAR_HASH); - } - if let Some(to) = expected_to { - expected_topics.push(encoded_into_hash(to)); - } else { - expected_topics.push(Hash::CLEAR_HASH); - } - expected_topics.push(encoded_into_hash(value)); - - let topics = event.topics.clone(); - for (n, (actual_topic, expected_topic)) in - topics.iter().zip(expected_topics).enumerate() - { - let mut topic_hash = Hash::CLEAR_HASH; - let len = actual_topic.len(); - topic_hash.as_mut()[0..len].copy_from_slice(&actual_topic[0..len]); - - assert_eq!( - topic_hash, expected_topic, - "encountered invalid topic at {n}" - ); - } - } - - /// The default constructor does its job. - #[ink::test] - fn new_works() { - // Constructor works. - let _erc20 = Erc20::new(100); - - // Transfer event triggered during initial construction. - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(1, emitted_events.len()); - - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - } - - /// The total supply was applied. - #[ink::test] - fn total_supply_works() { - // Constructor works. - let erc20 = Erc20::new(100); - // Transfer event triggered during initial construction. - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - // Get the token total supply. - assert_eq!(erc20.total_supply(), 100); - } - - /// Get the actual balance of an account. - #[ink::test] - fn balance_of_works() { - // Constructor works - let erc20 = Erc20::new(100); - // Transfer event triggered during initial construction - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - let accounts = - ink::env::test::default_accounts::(); - // Alice owns all the tokens on contract instantiation - assert_eq!(erc20.balance_of(accounts.alice), 100); - // Bob does not owns tokens - assert_eq!(erc20.balance_of(accounts.bob), 0); - } - - #[ink::test] - fn transfer_works() { - // Constructor works. - let mut erc20 = Erc20::new(100); - // Transfer event triggered during initial construction. - let accounts = - ink::env::test::default_accounts::(); - - assert_eq!(erc20.balance_of(accounts.bob), 0); - // Alice transfers 10 tokens to Bob. - assert_eq!(erc20.transfer(accounts.bob, 10), Ok(())); - // Bob owns 10 tokens. - assert_eq!(erc20.balance_of(accounts.bob), 10); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 2); - // Check first transfer event related to ERC-20 instantiation. - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - // Check the second transfer event relating to the actual trasfer. - assert_transfer_event( - &emitted_events[1], - Some(AccountId::from([0x01; 32])), - Some(AccountId::from([0x02; 32])), - 10, - ); - } - - #[ink::test] - fn invalid_transfer_should_fail() { - // Constructor works. - let mut erc20 = Erc20::new(100); - let accounts = - ink::env::test::default_accounts::(); - - assert_eq!(erc20.balance_of(accounts.bob), 0); - - // Set the contract as callee and Bob as caller. - let contract = ink::env::account_id::(); - ink::env::test::set_callee::(contract); - ink::env::test::set_caller::(accounts.bob); - - // Bob fails to transfers 10 tokens to Eve. - assert_eq!( - erc20.transfer(accounts.eve, 10), - Err(Error::InsufficientBalance) - ); - // Alice owns all the tokens. - assert_eq!(erc20.balance_of(accounts.alice), 100); - assert_eq!(erc20.balance_of(accounts.bob), 0); - assert_eq!(erc20.balance_of(accounts.eve), 0); - - // Transfer event triggered during initial construction. - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 1); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - } - - #[ink::test] - fn transfer_from_works() { - // Constructor works. - let mut erc20 = Erc20::new(100); - // Transfer event triggered during initial construction. - let accounts = - ink::env::test::default_accounts::(); - - // Bob fails to transfer tokens owned by Alice. - assert_eq!( - erc20.transfer_from(accounts.alice, accounts.eve, 10), - Err(Error::InsufficientAllowance) - ); - // Alice approves Bob for token transfers on her behalf. - assert_eq!(erc20.approve(accounts.bob, 10), Ok(())); - - // The approve event takes place. - assert_eq!(ink::env::test::recorded_events().count(), 2); - - // Set the contract as callee and Bob as caller. - let contract = ink::env::account_id::(); - ink::env::test::set_callee::(contract); - ink::env::test::set_caller::(accounts.bob); - - // Bob transfers tokens from Alice to Eve. - assert_eq!( - erc20.transfer_from(accounts.alice, accounts.eve, 10), - Ok(()) - ); - // Eve owns tokens. - assert_eq!(erc20.balance_of(accounts.eve), 10); - - // Check all transfer events that happened during the previous calls: - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 3); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - // The second event `emitted_events[1]` is an Approve event that we skip - // checking. - assert_transfer_event( - &emitted_events[2], - Some(AccountId::from([0x01; 32])), - Some(AccountId::from([0x05; 32])), - 10, - ); - } - - #[ink::test] - fn allowance_must_not_change_on_failed_transfer() { - let mut erc20 = Erc20::new(100); - let accounts = - ink::env::test::default_accounts::(); - - // Alice approves Bob for token transfers on her behalf. - let alice_balance = erc20.balance_of(accounts.alice); - let initial_allowance = alice_balance + 2; - assert_eq!(erc20.approve(accounts.bob, initial_allowance), Ok(())); - - // Get contract address. - let callee = ink::env::account_id::(); - ink::env::test::set_callee::(callee); - ink::env::test::set_caller::(accounts.bob); - - // Bob tries to transfer tokens from Alice to Eve. - let emitted_events_before = ink::env::test::recorded_events().count(); - assert_eq!( - erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1), - Err(Error::InsufficientBalance) - ); - // Allowance must have stayed the same - assert_eq!( - erc20.allowance(accounts.alice, accounts.bob), - initial_allowance - ); - // No more events must have been emitted - assert_eq!( - emitted_events_before, - ink::env::test::recorded_events().count() - ) - } - - fn encoded_into_hash(entity: T) -> Hash - where - T: ink::scale::Encode, - { - use ink::{ - env::hash::{ - Blake2x256, - CryptoHash, - HashOutput, - }, - primitives::Clear, - }; - - let mut result = Hash::CLEAR_HASH; - let len_result = result.as_ref().len(); - let encoded = entity.encode(); - let len_encoded = encoded.len(); - if len_encoded <= len_result { - result.as_mut()[..len_encoded].copy_from_slice(&encoded); - return result - } - let mut hash_output = - <::Type as Default>::default(); - ::hash(&encoded, &mut hash_output); - let copy_len = core::cmp::min(hash_output.len(), len_result); - result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]); - result - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_transfer(mut client: Client) -> E2EResult<()> { - // given - let total_supply = 1_000_000_000; - let mut constructor = Erc20Ref::new(total_supply); - let erc20 = client - .instantiate("erc20", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = erc20.call::(); - - // when - let total_supply_msg = call.total_supply(); - let total_supply_res = client - .call(&ink_e2e::bob(), &total_supply_msg) - .dry_run() - .await?; - - let bob_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Bob); - let transfer_to_bob = 500_000_000u128; - let transfer = call.transfer(bob_account, transfer_to_bob); - let _transfer_res = client - .call(&ink_e2e::alice(), &transfer) - .submit() - .await - .expect("transfer failed"); - - let balance_of = call.balance_of(bob_account); - let balance_of_res = client - .call(&ink_e2e::alice(), &balance_of) - .dry_run() - .await?; - - // then - assert_eq!( - total_supply, - total_supply_res.return_value(), - "total_supply" - ); - assert_eq!(transfer_to_bob, balance_of_res.return_value(), "balance_of"); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_allowances(mut client: Client) -> E2EResult<()> { - // given - let total_supply = 1_000_000_000; - let mut constructor = Erc20Ref::new(total_supply); - let erc20 = client - .instantiate("erc20", &ink_e2e::bob(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = erc20.call::(); - - // when - - let bob_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Bob); - let charlie_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Charlie); - - let amount = 500_000_000u128; - // tx - let transfer_from = call.transfer_from(bob_account, charlie_account, amount); - let transfer_from_result = client - .call(&ink_e2e::charlie(), &transfer_from) - .submit() - .await; - - assert!( - transfer_from_result.is_err(), - "unapproved transfer_from should fail" - ); - - // Bob approves Charlie to transfer up to amount on his behalf - let approved_value = 1_000u128; - let approve_call = call.approve(charlie_account, approved_value); - client - .call(&ink_e2e::bob(), &approve_call) - .submit() - .await - .expect("approve failed"); - - // `transfer_from` the approved amount - let transfer_from = - call.transfer_from(bob_account, charlie_account, approved_value); - let transfer_from_result = client - .call(&ink_e2e::charlie(), &transfer_from) - .submit() - .await; - assert!( - transfer_from_result.is_ok(), - "approved transfer_from should succeed" - ); - - let balance_of = call.balance_of(bob_account); - let balance_of_res = client - .call(&ink_e2e::alice(), &balance_of) - .dry_run() - .await?; - - // `transfer_from` again, this time exceeding the approved amount - let transfer_from = call.transfer_from(bob_account, charlie_account, 1); - let transfer_from_result = client - .call(&ink_e2e::charlie(), &transfer_from) - .submit() - .await; - assert!( - transfer_from_result.is_err(), - "transfer_from exceeding the approved amount should fail" - ); - - assert_eq!( - total_supply - approved_value, - balance_of_res.return_value(), - "balance_of" - ); - - Ok(()) - } - } -} diff --git a/integration-tests/erc721/.gitignore b/integration-tests/erc721/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/erc721/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/erc721/Cargo.toml b/integration-tests/erc721/Cargo.toml deleted file mode 100644 index 3895e12a470..00000000000 --- a/integration-tests/erc721/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "erc721" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/erc721/lib.rs b/integration-tests/erc721/lib.rs deleted file mode 100644 index 0bc5cddb034..00000000000 --- a/integration-tests/erc721/lib.rs +++ /dev/null @@ -1,637 +0,0 @@ -//! # ERC-721 -//! -//! This is an ERC-721 Token implementation. -//! -//! ## Warning -//! -//! This contract is an *example*. It is neither audited nor endorsed for production use. -//! Do **not** rely on it to keep anything of value secure. -//! -//! ## Overview -//! -//! This contract demonstrates how to build non-fungible or unique tokens using ink!. -//! -//! ## Error Handling -//! -//! Any function that modifies the state returns a `Result` type and does not changes the -//! state if the `Error` occurs. -//! The errors are defined as an `enum` type. Any other error or invariant violation -//! triggers a panic and therefore rolls back the transaction. -//! -//! ## Token Management -//! -//! After creating a new token, the function caller becomes the owner. -//! A token can be created, transferred, or destroyed. -//! -//! Token owners can assign other accounts for transferring specific tokens on their -//! behalf. It is also possible to authorize an operator (higher rights) for another -//! account to handle tokens. -//! -//! ### Token Creation -//! -//! Token creation start by calling the `mint(&mut self, id: u32)` function. -//! The token owner becomes the function caller. The Token ID needs to be specified -//! as the argument on this function call. -//! -//! ### Token Transfer -//! -//! Transfers may be initiated by: -//! - The owner of a token -//! - The approved address of a token -//! - An authorized operator of the current owner of a token -//! -//! The token owner can transfer a token by calling the `transfer` or `transfer_from` -//! functions. An approved address can make a token transfer by calling the -//! `transfer_from` function. Operators can transfer tokens on another account's behalf or -//! can approve a token transfer for a different account. -//! -//! ### Token Removal -//! -//! Tokens can be destroyed by burning them. Only the token owner is allowed to burn a -//! token. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod erc721 { - use ink::storage::Mapping; - - /// A token ID. - pub type TokenId = u32; - - #[ink(storage)] - #[derive(Default)] - pub struct Erc721 { - /// Mapping from token to owner. - token_owner: Mapping, - /// Mapping from token to approvals users. - token_approvals: Mapping, - /// Mapping from owner to number of owned token. - owned_tokens_count: Mapping, - /// Mapping from owner to operator approvals. - operator_approvals: Mapping<(AccountId, AccountId), ()>, - } - - #[derive(Debug, PartialEq, Eq, Copy, Clone)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Error { - NotOwner, - NotApproved, - TokenExists, - TokenNotFound, - CannotInsert, - CannotFetchValue, - NotAllowed, - } - - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - #[ink(topic)] - id: TokenId, - } - - /// Event emitted when a token approve occurs. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - from: AccountId, - #[ink(topic)] - to: AccountId, - #[ink(topic)] - id: TokenId, - } - - /// Event emitted when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - #[ink(event)] - pub struct ApprovalForAll { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - operator: AccountId, - approved: bool, - } - - impl Erc721 { - /// Creates a new ERC-721 token contract. - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - /// Returns the balance of the owner. - /// - /// This represents the amount of unique tokens the owner has. - #[ink(message)] - pub fn balance_of(&self, owner: AccountId) -> u32 { - self.balance_of_or_zero(&owner) - } - - /// Returns the owner of the token. - #[ink(message)] - pub fn owner_of(&self, id: TokenId) -> Option { - self.token_owner.get(id) - } - - /// Returns the approved account ID for this token if any. - #[ink(message)] - pub fn get_approved(&self, id: TokenId) -> Option { - self.token_approvals.get(id) - } - - /// Returns `true` if the operator is approved by the owner. - #[ink(message)] - pub fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { - self.approved_for_all(owner, operator) - } - - /// Approves or disapproves the operator for all tokens of the caller. - #[ink(message)] - pub fn set_approval_for_all( - &mut self, - to: AccountId, - approved: bool, - ) -> Result<(), Error> { - self.approve_for_all(to, approved)?; - Ok(()) - } - - /// Approves the account to transfer the specified token on behalf of the caller. - #[ink(message)] - pub fn approve(&mut self, to: AccountId, id: TokenId) -> Result<(), Error> { - self.approve_for(&to, id)?; - Ok(()) - } - - /// Transfers the token from the caller to the given destination. - #[ink(message)] - pub fn transfer( - &mut self, - destination: AccountId, - id: TokenId, - ) -> Result<(), Error> { - let caller = self.env().caller(); - self.transfer_token_from(&caller, &destination, id)?; - Ok(()) - } - - /// Transfer approved or owned token. - #[ink(message)] - pub fn transfer_from( - &mut self, - from: AccountId, - to: AccountId, - id: TokenId, - ) -> Result<(), Error> { - self.transfer_token_from(&from, &to, id)?; - Ok(()) - } - - /// Creates a new token. - #[ink(message)] - pub fn mint(&mut self, id: TokenId) -> Result<(), Error> { - let caller = self.env().caller(); - self.add_token_to(&caller, id)?; - self.env().emit_event(Transfer { - from: Some(AccountId::from([0x0; 32])), - to: Some(caller), - id, - }); - Ok(()) - } - - /// Deletes an existing token. Only the owner can burn the token. - #[ink(message)] - pub fn burn(&mut self, id: TokenId) -> Result<(), Error> { - let caller = self.env().caller(); - let Self { - token_owner, - owned_tokens_count, - .. - } = self; - - let owner = token_owner.get(id).ok_or(Error::TokenNotFound)?; - if owner != caller { - return Err(Error::NotOwner) - }; - - let count = owned_tokens_count - .get(caller) - .map(|c| c.checked_sub(1).unwrap()) - .ok_or(Error::CannotFetchValue)?; - owned_tokens_count.insert(caller, &count); - token_owner.remove(id); - - self.env().emit_event(Transfer { - from: Some(caller), - to: Some(AccountId::from([0x0; 32])), - id, - }); - - Ok(()) - } - - /// Transfers token `id` `from` the sender to the `to` `AccountId`. - fn transfer_token_from( - &mut self, - from: &AccountId, - to: &AccountId, - id: TokenId, - ) -> Result<(), Error> { - let caller = self.env().caller(); - if !self.exists(id) { - return Err(Error::TokenNotFound) - }; - if !self.approved_or_owner(Some(caller), id) { - return Err(Error::NotApproved) - }; - self.clear_approval(id); - self.remove_token_from(from, id)?; - self.add_token_to(to, id)?; - self.env().emit_event(Transfer { - from: Some(*from), - to: Some(*to), - id, - }); - Ok(()) - } - - /// Removes token `id` from the owner. - fn remove_token_from( - &mut self, - from: &AccountId, - id: TokenId, - ) -> Result<(), Error> { - let Self { - token_owner, - owned_tokens_count, - .. - } = self; - - if !token_owner.contains(id) { - return Err(Error::TokenNotFound) - } - - let count = owned_tokens_count - .get(from) - .map(|c| c.checked_sub(1).unwrap()) - .ok_or(Error::CannotFetchValue)?; - owned_tokens_count.insert(from, &count); - token_owner.remove(id); - - Ok(()) - } - - /// Adds the token `id` to the `to` AccountID. - fn add_token_to(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> { - let Self { - token_owner, - owned_tokens_count, - .. - } = self; - - if token_owner.contains(id) { - return Err(Error::TokenExists) - } - - if *to == AccountId::from([0x0; 32]) { - return Err(Error::NotAllowed) - }; - - let count = owned_tokens_count - .get(to) - .map(|c| c.checked_add(1).unwrap()) - .unwrap_or(1); - - owned_tokens_count.insert(to, &count); - token_owner.insert(id, to); - - Ok(()) - } - - /// Approves or disapproves the operator to transfer all tokens of the caller. - fn approve_for_all( - &mut self, - to: AccountId, - approved: bool, - ) -> Result<(), Error> { - let caller = self.env().caller(); - if to == caller { - return Err(Error::NotAllowed) - } - self.env().emit_event(ApprovalForAll { - owner: caller, - operator: to, - approved, - }); - - if approved { - self.operator_approvals.insert((&caller, &to), &()); - } else { - self.operator_approvals.remove((&caller, &to)); - } - - Ok(()) - } - - /// Approve the passed `AccountId` to transfer the specified token on behalf of - /// the message's sender. - fn approve_for(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> { - let caller = self.env().caller(); - let owner = self.owner_of(id); - if !(owner == Some(caller) - || self.approved_for_all(owner.expect("Error with AccountId"), caller)) - { - return Err(Error::NotAllowed) - }; - - if *to == AccountId::from([0x0; 32]) { - return Err(Error::NotAllowed) - }; - - if self.token_approvals.contains(id) { - return Err(Error::CannotInsert) - } else { - self.token_approvals.insert(id, to); - } - - self.env().emit_event(Approval { - from: caller, - to: *to, - id, - }); - - Ok(()) - } - - /// Removes existing approval from token `id`. - fn clear_approval(&mut self, id: TokenId) { - self.token_approvals.remove(id); - } - - // Returns the total number of tokens from an account. - fn balance_of_or_zero(&self, of: &AccountId) -> u32 { - self.owned_tokens_count.get(of).unwrap_or(0) - } - - /// Gets an operator on other Account's behalf. - fn approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { - self.operator_approvals.contains((&owner, &operator)) - } - - /// Returns true if the `AccountId` `from` is the owner of token `id` - /// or it has been approved on behalf of the token `id` owner. - fn approved_or_owner(&self, from: Option, id: TokenId) -> bool { - let owner = self.owner_of(id); - from != Some(AccountId::from([0x0; 32])) - && (from == owner - || from == self.token_approvals.get(id) - || self.approved_for_all( - owner.expect("Error with AccountId"), - from.expect("Error with AccountId"), - )) - } - - /// Returns true if token `id` exists or false if it does not. - fn exists(&self, id: TokenId) -> bool { - self.token_owner.contains(id) - } - } - - /// Unit tests - #[cfg(test)] - mod tests { - /// Imports all the definitions from the outer scope so we can use them here. - use super::*; - - #[ink::test] - fn mint_works() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Token 1 does not exists. - assert_eq!(erc721.owner_of(1), None); - // Alice does not owns tokens. - assert_eq!(erc721.balance_of(accounts.alice), 0); - // Create token Id 1. - assert_eq!(erc721.mint(1), Ok(())); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - } - - #[ink::test] - fn mint_existing_should_fail() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1. - assert_eq!(erc721.mint(1), Ok(())); - // The first Transfer event takes place - assert_eq!(1, ink::env::test::recorded_events().count()); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Alice owns token Id 1. - assert_eq!(erc721.owner_of(1), Some(accounts.alice)); - // Cannot create token Id if it exists. - // Bob cannot own token Id 1. - assert_eq!(erc721.mint(1), Err(Error::TokenExists)); - } - - #[ink::test] - fn transfer_works() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1 for Alice - assert_eq!(erc721.mint(1), Ok(())); - // Alice owns token 1 - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Bob does not owns any token - assert_eq!(erc721.balance_of(accounts.bob), 0); - // The first Transfer event takes place - assert_eq!(1, ink::env::test::recorded_events().count()); - // Alice transfers token 1 to Bob - assert_eq!(erc721.transfer(accounts.bob, 1), Ok(())); - // The second Transfer event takes place - assert_eq!(2, ink::env::test::recorded_events().count()); - // Bob owns token 1 - assert_eq!(erc721.balance_of(accounts.bob), 1); - } - - #[ink::test] - fn invalid_transfer_should_fail() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Transfer token fails if it does not exists. - assert_eq!(erc721.transfer(accounts.bob, 2), Err(Error::TokenNotFound)); - // Token Id 2 does not exists. - assert_eq!(erc721.owner_of(2), None); - // Create token Id 2. - assert_eq!(erc721.mint(2), Ok(())); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Token Id 2 is owned by Alice. - assert_eq!(erc721.owner_of(2), Some(accounts.alice)); - // Set Bob as caller - set_caller(accounts.bob); - // Bob cannot transfer not owned tokens. - assert_eq!(erc721.transfer(accounts.eve, 2), Err(Error::NotApproved)); - } - - #[ink::test] - fn approved_transfer_works() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1. - assert_eq!(erc721.mint(1), Ok(())); - // Token Id 1 is owned by Alice. - assert_eq!(erc721.owner_of(1), Some(accounts.alice)); - // Approve token Id 1 transfer for Bob on behalf of Alice. - assert_eq!(erc721.approve(accounts.bob, 1), Ok(())); - // Set Bob as caller - set_caller(accounts.bob); - // Bob transfers token Id 1 from Alice to Eve. - assert_eq!( - erc721.transfer_from(accounts.alice, accounts.eve, 1), - Ok(()) - ); - // TokenId 3 is owned by Eve. - assert_eq!(erc721.owner_of(1), Some(accounts.eve)); - // Alice does not owns tokens. - assert_eq!(erc721.balance_of(accounts.alice), 0); - // Bob does not owns tokens. - assert_eq!(erc721.balance_of(accounts.bob), 0); - // Eve owns 1 token. - assert_eq!(erc721.balance_of(accounts.eve), 1); - } - - #[ink::test] - fn approved_for_all_works() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1. - assert_eq!(erc721.mint(1), Ok(())); - // Create token Id 2. - assert_eq!(erc721.mint(2), Ok(())); - // Alice owns 2 tokens. - assert_eq!(erc721.balance_of(accounts.alice), 2); - // Approve token Id 1 transfer for Bob on behalf of Alice. - assert_eq!(erc721.set_approval_for_all(accounts.bob, true), Ok(())); - // Bob is an approved operator for Alice - assert!(erc721.is_approved_for_all(accounts.alice, accounts.bob)); - // Set Bob as caller - set_caller(accounts.bob); - // Bob transfers token Id 1 from Alice to Eve. - assert_eq!( - erc721.transfer_from(accounts.alice, accounts.eve, 1), - Ok(()) - ); - // TokenId 1 is owned by Eve. - assert_eq!(erc721.owner_of(1), Some(accounts.eve)); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Bob transfers token Id 2 from Alice to Eve. - assert_eq!( - erc721.transfer_from(accounts.alice, accounts.eve, 2), - Ok(()) - ); - // Bob does not own tokens. - assert_eq!(erc721.balance_of(accounts.bob), 0); - // Eve owns 2 tokens. - assert_eq!(erc721.balance_of(accounts.eve), 2); - // Remove operator approval for Bob on behalf of Alice. - set_caller(accounts.alice); - assert_eq!(erc721.set_approval_for_all(accounts.bob, false), Ok(())); - // Bob is not an approved operator for Alice. - assert!(!erc721.is_approved_for_all(accounts.alice, accounts.bob)); - } - - #[ink::test] - fn not_approved_transfer_should_fail() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1. - assert_eq!(erc721.mint(1), Ok(())); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Bob does not owns tokens. - assert_eq!(erc721.balance_of(accounts.bob), 0); - // Eve does not owns tokens. - assert_eq!(erc721.balance_of(accounts.eve), 0); - // Set Eve as caller - set_caller(accounts.eve); - // Eve is not an approved operator by Alice. - assert_eq!( - erc721.transfer_from(accounts.alice, accounts.frank, 1), - Err(Error::NotApproved) - ); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Bob does not owns tokens. - assert_eq!(erc721.balance_of(accounts.bob), 0); - // Eve does not owns tokens. - assert_eq!(erc721.balance_of(accounts.eve), 0); - } - - #[ink::test] - fn burn_works() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1 for Alice - assert_eq!(erc721.mint(1), Ok(())); - // Alice owns 1 token. - assert_eq!(erc721.balance_of(accounts.alice), 1); - // Alice owns token Id 1. - assert_eq!(erc721.owner_of(1), Some(accounts.alice)); - // Destroy token Id 1. - assert_eq!(erc721.burn(1), Ok(())); - // Alice does not owns tokens. - assert_eq!(erc721.balance_of(accounts.alice), 0); - // Token Id 1 does not exists - assert_eq!(erc721.owner_of(1), None); - } - - #[ink::test] - fn burn_fails_token_not_found() { - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Try burning a non existent token - assert_eq!(erc721.burn(1), Err(Error::TokenNotFound)); - } - - #[ink::test] - fn burn_fails_not_owner() { - let accounts = - ink::env::test::default_accounts::(); - // Create a new contract instance. - let mut erc721 = Erc721::new(); - // Create token Id 1 for Alice - assert_eq!(erc721.mint(1), Ok(())); - // Try burning this token with a different account - set_caller(accounts.eve); - assert_eq!(erc721.burn(1), Err(Error::NotOwner)); - } - - fn set_caller(sender: AccountId) { - ink::env::test::set_caller::(sender); - } - } -} diff --git a/integration-tests/events/.gitignore b/integration-tests/events/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/events/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/events/Cargo.toml b/integration-tests/events/Cargo.toml deleted file mode 100644 index ebc1e837151..00000000000 --- a/integration-tests/events/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "events" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -event-def = { path = "event-def", default-features = false } -event-def2 = { path = "event-def2", default-features = false } -event-def-unused = { path = "event-def-unused", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "event-def/std", - "event-def2/std", - "event-def-unused/std", -] -ink-as-dependency = [] -e2e-tests = [] - -[profile.test] -# Need this for linkme crate to work for the event metadata unit test. -# See https://github.com/dtolnay/linkme/issues/61#issuecomment-1503653702 -lto = "thin" diff --git a/integration-tests/events/event-def-unused/Cargo.toml b/integration-tests/events/event-def-unused/Cargo.toml deleted file mode 100644 index 1bdfc90af33..00000000000 --- a/integration-tests/events/event-def-unused/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "event-def-unused" -version = "0.1.0" -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[features] -default = ["std"] -std = [ - "ink/std", -] diff --git a/integration-tests/events/event-def-unused/src/lib.rs b/integration-tests/events/event-def-unused/src/lib.rs deleted file mode 100644 index cbf2d719fd8..00000000000 --- a/integration-tests/events/event-def-unused/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::trait_definition] -pub trait FlipperTrait { - #[ink(message)] - fn flip(&mut self); -} - -#[ink::event] -pub struct EventDefUnused { - #[ink(topic)] - pub hash: [u8; 32], - #[ink(topic)] - pub maybe_hash: Option<[u8; 32]>, -} diff --git a/integration-tests/events/event-def/Cargo.toml b/integration-tests/events/event-def/Cargo.toml deleted file mode 100644 index 902d532e8ff..00000000000 --- a/integration-tests/events/event-def/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "event-def" -version = "0.1.0" -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[features] -default = ["std"] -std = [ - "ink/std", -] diff --git a/integration-tests/events/event-def/src/lib.rs b/integration-tests/events/event-def/src/lib.rs deleted file mode 100644 index 4f9e8ffac10..00000000000 --- a/integration-tests/events/event-def/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::event] -pub struct ForeignFlipped { - pub value: bool, -} - -#[ink::event] -pub struct ThirtyTwoByteTopics { - #[ink(topic)] - pub hash: [u8; 32], - #[ink(topic)] - pub maybe_hash: Option<[u8; 32]>, -} diff --git a/integration-tests/events/event-def2/Cargo.toml b/integration-tests/events/event-def2/Cargo.toml deleted file mode 100644 index 18bc859173d..00000000000 --- a/integration-tests/events/event-def2/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "event-def2" -version = "0.1.0" -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[features] -default = ["std"] -std = [ - "ink/std", -] diff --git a/integration-tests/events/event-def2/src/lib.rs b/integration-tests/events/event-def2/src/lib.rs deleted file mode 100644 index f27050f895d..00000000000 --- a/integration-tests/events/event-def2/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::event] -pub struct EventDefAnotherCrate { - #[ink(topic)] - pub hash: [u8; 32], - #[ink(topic)] - pub maybe_hash: Option<[u8; 32]>, -} diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs deleted file mode 100644 index 2fa51eafdcf..00000000000 --- a/integration-tests/events/lib.rs +++ /dev/null @@ -1,415 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::event(anonymous = true)] -pub struct AnonymousEvent { - #[ink(topic)] - pub topic: [u8; 32], - pub field_1: u32, -} - -#[ink::contract] -pub mod events { - #[ink(storage)] - pub struct Events { - value: bool, - } - - #[ink(event)] - pub struct InlineFlipped { - value: bool, - } - - #[ink( - event, - signature_topic = "1111111111111111111111111111111111111111111111111111111111111111" - )] - pub struct InlineCustomFlipped { - value: bool, - } - - #[ink(event)] - #[ink(anonymous)] - pub struct InlineAnonymousEvent { - #[ink(topic)] - pub topic: [u8; 32], - pub field_1: u32, - } - - impl Events { - /// Creates a new events smart contract initialized with the given value. - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { value: init_value } - } - - /// Flips the current value of the boolean. - #[ink(message)] - pub fn flip_with_foreign_event(&mut self) { - self.value = !self.value; - self.env() - .emit_event(event_def::ForeignFlipped { value: self.value }) - } - - /// Flips the current value of the boolean. - #[ink(message)] - pub fn flip_with_inline_event(&mut self) { - self.value = !self.value; - self.env().emit_event(InlineFlipped { value: self.value }) - } - - /// Flips the current value of the boolean. - #[ink(message)] - pub fn flip_with_inline_custom_event(&mut self) { - self.value = !self.value; - self.env() - .emit_event(InlineCustomFlipped { value: self.value }) - } - - /// Emit an event with a 32 byte topic. - #[ink(message)] - pub fn emit_32_byte_topic_event(&self, maybe_hash: Option<[u8; 32]>) { - self.env().emit_event(event_def::ThirtyTwoByteTopics { - hash: [0x42; 32], - maybe_hash, - }) - } - - /// Emit an event from a different crate. - #[ink(message)] - pub fn emit_event_from_a_different_crate(&self, maybe_hash: Option<[u8; 32]>) { - self.env().emit_event(event_def2::EventDefAnotherCrate { - hash: [0x42; 32], - maybe_hash, - }) - } - - /// Emit a inline and standalone anonymous events - #[ink(message)] - pub fn emit_anonymous_events(&self, topic: [u8; 32]) { - self.env() - .emit_event(InlineAnonymousEvent { topic, field_1: 42 }); - self.env() - .emit_event(super::AnonymousEvent { topic, field_1: 42 }); - } - } - - /// Implementing the trait from the `event_def_unused` crate includes all defined - /// events there. - impl event_def_unused::FlipperTrait for Events { - #[ink(message)] - fn flip(&mut self) { - self.value = !self.value; - } - } - - #[cfg(test)] - mod tests { - use super::*; - use ink::scale::Decode as _; - - #[test] - fn collects_specs_for_all_linked_and_used_events() { - let event_specs = ink::metadata::collect_events(); - assert_eq!(8, event_specs.len()); - - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"ForeignFlipped")); - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"InlineFlipped")); - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"InlineCustomFlipped")); - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"ThirtyTwoByteTopics")); - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"EventDefAnotherCrate")); - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"AnonymousEvent")); - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"InlineAnonymousEvent")); - - // The event is not used in the code by being included in the metadata - // because we implement trait form `event_def_unused` crate. - assert!(event_specs - .iter() - .any(|evt| evt.label() == &"EventDefUnused")); - } - - #[ink::test] - fn it_works() { - let mut events = Events::new(false); - events.flip_with_foreign_event(); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(1, emitted_events.len()); - let event = &emitted_events[0]; - - let decoded_event = ::decode(&mut &event.data[..]) - .expect("encountered invalid contract event data buffer"); - assert!(decoded_event.value); - } - - #[ink::test] - fn option_topic_some_has_topic() { - let events = Events::new(false); - events.emit_32_byte_topic_event(Some([0xAA; 32])); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(1, emitted_events.len()); - let event = &emitted_events[0]; - - assert_eq!(event.topics.len(), 3); - let signature_topic = - ::SIGNATURE_TOPIC - .map(|topic| topic.to_vec()); - assert_eq!(Some(&event.topics[0]), signature_topic.as_ref()); - assert_eq!(event.topics[1], [0x42; 32]); - assert_eq!( - event.topics[2], [0xAA; 32], - "option topic should be published" - ); - } - - #[ink::test] - fn option_topic_none_encoded_as_0() { - let events = Events::new(false); - events.emit_32_byte_topic_event(None); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(1, emitted_events.len()); - let event = &emitted_events[0]; - - let signature_topic = - ::SIGNATURE_TOPIC - .map(|topic| topic.to_vec()) - .unwrap(); - - let expected_topics = vec![ - signature_topic, - [0x42; 32].to_vec(), - [0x00; 32].to_vec(), // None is encoded as 0x00 - ]; - assert_eq!(expected_topics, event.topics); - } - - #[ink::test] - fn custom_signature_topic() { - let mut events = Events::new(false); - events.flip_with_inline_custom_event(); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(1, emitted_events.len()); - - let signature_topic = - ::SIGNATURE_TOPIC; - - assert_eq!(Some([17u8; 32]), signature_topic); - } - - #[ink::test] - fn anonymous_events_emit_no_signature_topics() { - let events = Events::new(false); - let topic = [0x42; 32]; - events.emit_anonymous_events(topic); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(2, emitted_events.len()); - - let event = &emitted_events[0]; - assert_eq!(event.topics.len(), 1); - assert_eq!(event.topics[0], topic); - - let event = &emitted_events[1]; - assert_eq!(event.topics.len(), 1); - assert_eq!(event.topics[0], topic); - - let signature_topic = - ::SIGNATURE_TOPIC; - assert_eq!(None, signature_topic); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - ContractsBackend, - H256, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn emits_foreign_event( - mut client: Client, - ) -> E2EResult<()> { - // given - let init_value = false; - let mut constructor = EventsRef::new(init_value); - let contract = client - .instantiate("events", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let flip = call.flip_with_foreign_event(); - let flip_res = client - .call(&ink_e2e::bob(), &flip) - .submit() - .await - .expect("flip failed"); - - let contract_events = flip_res.contract_emitted_events()?; - - // then - assert_eq!(1, contract_events.len()); - let contract_event = &contract_events[0]; - let flipped: event_def::ForeignFlipped = - ink::scale::Decode::decode(&mut &contract_event.event.data[..]) - .expect("encountered invalid contract event data buffer"); - assert_eq!(!init_value, flipped.value); - - let signature_topic = - ::SIGNATURE_TOPIC - .map(H256::from) - .unwrap(); - - let expected_topics = vec![signature_topic]; - assert_eq!(expected_topics, contract_event.topics); - - Ok(()) - } - - #[ink_e2e::test] - async fn emits_inline_event( - mut client: Client, - ) -> E2EResult<()> { - // given - let init_value = false; - let mut constructor = EventsRef::new(init_value); - let contract = client - .instantiate("events", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let flip = call.flip_with_inline_event(); - let flip_res = client - .call(&ink_e2e::bob(), &flip) - .submit() - .await - .expect("flip failed"); - - let contract_events = flip_res.contract_emitted_events()?; - - // then - assert_eq!(1, contract_events.len()); - let contract_event = &contract_events[0]; - let flipped: InlineFlipped = - ink::scale::Decode::decode(&mut &contract_event.event.data[..]) - .expect("encountered invalid contract event data buffer"); - assert_eq!(!init_value, flipped.value); - - let signature_topic = ::SIGNATURE_TOPIC - .map(H256::from) - .unwrap(); - - let expected_topics = vec![signature_topic]; - assert_eq!(expected_topics, contract_event.topics); - - Ok(()) - } - - #[ink_e2e::test] - async fn emits_event_with_option_topic_none( - mut client: Client, - ) -> E2EResult<()> { - // given - let init_value = false; - let mut constructor = EventsRef::new(init_value); - let contract = client - .instantiate("events", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - // when - let call = call.emit_32_byte_topic_event(None); - let call_res = client - .call(&ink_e2e::bob(), &call) - .submit() - .await - .expect("emit_32_byte_topic_event failed"); - - let contract_events = call_res.contract_emitted_events()?; - - // then - assert_eq!(1, contract_events.len()); - let contract_event = &contract_events[0]; - let event: event_def::ThirtyTwoByteTopics = - ink::scale::Decode::decode(&mut &contract_event.event.data[..]) - .expect("encountered invalid contract event data buffer"); - assert!(event.maybe_hash.is_none()); - - let signature_topic = - ::SIGNATURE_TOPIC - .map(H256::from) - .unwrap(); - - let expected_topics = vec![ - signature_topic, - [0x42; 32].into(), - [0x00; 32].into(), // None is encoded as 0x00 - ]; - assert_eq!(expected_topics, contract_event.topics); - - Ok(()) - } - - #[ink_e2e::test] - async fn emits_custom_signature_event( - mut client: Client, - ) -> E2EResult<()> { - // given - let init_value = false; - let mut constructor = EventsRef::new(init_value); - let contract = client - .instantiate("events", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let call = call.flip_with_inline_custom_event(); - let call_res = client - .call(&ink_e2e::bob(), &call) - .submit() - .await - .expect("flip_with_inline_custom_event failed"); - - let contract_events = call_res.contract_emitted_events()?; - - // then - assert_eq!(1, contract_events.len()); - - let signature_topic = - ::SIGNATURE_TOPIC; - - assert_eq!(Some([17u8; 32]), signature_topic); - - Ok(()) - } - } -} diff --git a/integration-tests/incrementer/.gitignore b/integration-tests/incrementer/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/incrementer/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/incrementer/Cargo.toml b/integration-tests/incrementer/Cargo.toml deleted file mode 100644 index 8080f0be9fe..00000000000 --- a/integration-tests/incrementer/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "incrementer" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/incrementer/lib.rs b/integration-tests/incrementer/lib.rs deleted file mode 100644 index 5163ecd4a3b..00000000000 --- a/integration-tests/incrementer/lib.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::incrementer::{ - Incrementer, - IncrementerRef, -}; - -#[ink::contract] -mod incrementer { - #[ink(storage)] - pub struct Incrementer { - value: i32, - } - - impl Incrementer { - #[ink(constructor)] - pub fn new(init_value: i32) -> Self { - Self { value: init_value } - } - - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - #[ink(message)] - pub fn inc(&mut self, by: i32) { - self.value = self.value.checked_add(by).unwrap(); - } - - #[ink(message)] - pub fn get(&self) -> i32 { - self.value - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - let contract = Incrementer::new_default(); - assert_eq!(contract.get(), 0); - } - - #[ink::test] - fn it_works() { - let mut contract = Incrementer::new(42); - assert_eq!(contract.get(), 42); - contract.inc(5); - assert_eq!(contract.get(), 47); - contract.inc(-50); - assert_eq!(contract.get(), -3); - } - } -} diff --git a/integration-tests/lang-err-integration-tests/.gitignore b/integration-tests/lang-err-integration-tests/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/lang-err-integration-tests/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml b/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml deleted file mode 100755 index b311b634e65..00000000000 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "call_builder_delegate" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -incrementer = { path = "../../incrementer", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "incrementer/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs deleted file mode 100755 index af94928e65e..00000000000 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! # Integration Tests for `LangError` -//! -//! This contract is used to ensure that the behavior around `LangError`s works as -//! expected. -//! -//! In particular, it exercises the codepaths that stem from the usage of the -//! [`CallBuilder`](`ink::env::call::CallBuilder`) and -//! [`CreateBuilder`](`ink::env::call::CreateBuilder`) structs. -//! -//! This differs from the codepath used by external tooling, such as `cargo-contract` or -//! the `Contracts-UI` which instead depend on methods from the Contracts pallet which are -//! exposed via RPC. -//! -//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure -//! that you have a node which includes the Contracts pallet running alongside your tests. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod call_builder { - use ink::env::{ - call::{ - build_call, - ExecutionInput, - Selector, - }, - DefaultEnvironment, - }; - - #[ink(storage)] - #[derive(Default)] - pub struct CallBuilderDelegateTest { - /// Since we're going to `DelegateCall` into the `incrementer` contract, we need - /// to make sure our storage layout matches. - value: i32, - } - - impl CallBuilderDelegateTest { - #[ink(constructor)] - pub fn new(value: i32) -> Self { - Self { value } - } - - /// Call a contract using the `CallBuilder`. - /// - /// Since we can't use the `CallBuilder` in a test environment directly we need - /// this wrapper to test things like crafting calls with invalid - /// selectors. - /// - /// We also wrap the output in an `Option` since we can't return a `Result` - /// directly from a contract message without erroring out ourselves. - #[ink(message)] - pub fn delegate( - &mut self, - code_hash: Hash, - selector: [u8; 4], - ) -> Option { - let result = build_call::() - .delegate(code_hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .try_invoke() - .expect("Error from the Contracts pallet."); - - match result { - Ok(_) => None, - Err(e @ ink::LangError::CouldNotReadInput) => Some(e), - Err(_) => { - unimplemented!("No other `LangError` variants exist at the moment.") - } - } - } - - /// Call a contract using the `CallBuilder`. - /// - /// Since we can't use the `CallBuilder` in a test environment directly we need - /// this wrapper to test things like crafting calls with invalid - /// selectors. - /// - /// This message does not allow the caller to handle any `LangErrors`, for that - /// use the `call` message instead. - #[ink(message)] - pub fn invoke(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { - use ink::env::call::build_call; - - build_call::() - .delegate(code_hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .invoke() - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - ChainBackend, - ContractsBackend, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_invalid_message_selector_can_be_handled( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderDelegateTestRef::new(Default::default()); - let call_builder_contract = client - .instantiate("call_builder_delegate", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = - call_builder_contract.call::(); - - let code_hash = client - .upload("incrementer", &origin) - .submit() - .await - .expect("upload `incrementer` failed") - .code_hash; - - let selector = ink::selector_bytes!("invalid_selector"); - let call = call_builder_call.delegate(code_hash, selector); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Calling `call_builder::delegate` failed"); - - assert!(matches!( - call_result.return_value(), - Some(ink::LangError::CouldNotReadInput) - )); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_invalid_message_selector_panics_on_invoke( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::charlie(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderDelegateTestRef::new(Default::default()); - let call_builder_contract = client - .instantiate("call_builder_delegate", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = - call_builder_contract.call::(); - - let code_hash = client - .upload("incrementer", &origin) - .submit() - .await - .expect("upload `incrementer` failed") - .code_hash; - - // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method - // we expect this to panic. - let selector = ink::selector_bytes!("invalid_selector"); - let call = call_builder_call.invoke(code_hash, selector); - let call_result = client.call(&origin, &call).dry_run().await; - - if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { - assert!(dry_run - .debug_message - .contains("Cross-contract call failed with CouldNotReadInput")); - } else { - panic!("Expected call to fail"); - } - - Ok(()) - } - } -} diff --git a/integration-tests/lang-err-integration-tests/call-builder/Cargo.toml b/integration-tests/lang-err-integration-tests/call-builder/Cargo.toml deleted file mode 100755 index 823cb5e1ed6..00000000000 --- a/integration-tests/lang-err-integration-tests/call-builder/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "call_builder" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -constructors_return_value = { path = "../constructors-return-value", default-features = false, features = ["ink-as-dependency"] } -integration_flipper = { path = "../integration-flipper", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "constructors_return_value/std", - "integration_flipper/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/call-builder/lib.rs b/integration-tests/lang-err-integration-tests/call-builder/lib.rs deleted file mode 100755 index d924b5829b7..00000000000 --- a/integration-tests/lang-err-integration-tests/call-builder/lib.rs +++ /dev/null @@ -1,587 +0,0 @@ -//! # Integration Tests for `LangError` -//! -//! This contract is used to ensure that the behavior around `LangError`s works as -//! expected. -//! -//! In particular, it exercises the codepaths that stem from the usage of the -//! [`CallBuilder`](`ink::env::call::CallBuilder`) and -//! [`CreateBuilder`](`ink::env::call::CreateBuilder`) structs. -//! -//! This differs from the codepath used by external tooling, such as `cargo-contract` or -//! the `Contracts-UI` which instead depend on methods from the Contracts pallet which are -//! exposed via RPC. -//! -//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure -//! that you have a node which includes the Contracts pallet running alongside your tests. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod call_builder { - use constructors_return_value::ConstructorsReturnValueRef; - use ink::env::{ - call::{ - build_call, - ExecutionInput, - Selector, - }, - DefaultEnvironment, - }; - - #[ink(storage)] - #[derive(Default)] - pub struct CallBuilderTest {} - - impl CallBuilderTest { - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - /// Call a contract using the `CallBuilder`. - /// - /// Since we can't use the `CallBuilder` in a test environment directly we need - /// this wrapper to test things like crafting calls with invalid - /// selectors. - /// - /// We also wrap the output in an `Option` since we can't return a `Result` - /// directly from a contract message without erroring out ourselves. - #[ink(message)] - pub fn call( - &mut self, - address: AccountId, - selector: [u8; 4], - ) -> Option { - let result = build_call::() - .call(address) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::<()>() - .try_invoke() - .expect("Error from the Contracts pallet."); - - match result { - Ok(_) => None, - Err(e @ ink::LangError::CouldNotReadInput) => Some(e), - Err(_) => { - unimplemented!("No other `LangError` variants exist at the moment.") - } - } - } - - /// Call a contract using the `CallBuilder`. - /// - /// Since we can't use the `CallBuilder` in a test environment directly we need - /// this wrapper to test things like crafting calls with invalid - /// selectors. - /// - /// This message does not allow the caller to handle any `LangErrors`, for that - /// use the `call` message instead. - #[ink(message)] - pub fn invoke(&mut self, address: AccountId, selector: [u8; 4]) { - use ink::env::call::build_call; - - build_call::() - .call(address) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::<()>() - .invoke() - } - - /// Instantiate a contract using the `CreateBuilder`. - /// - /// Since we can't use the `CreateBuilder` in a test environment directly we need - /// this wrapper to test things like crafting calls with invalid - /// selectors. - /// - /// We also wrap the output in an `Option` since we can't return a `Result` - /// directly from a contract message without erroring out ourselves. - #[ink(message)] - pub fn call_instantiate( - &mut self, - code_hash: Hash, - selector: [u8; 4], - init_value: bool, - ) -> Option { - let mut params = ConstructorsReturnValueRef::new(init_value) - .code_hash(code_hash) - .gas_limit(0) - .endowment(0) - .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) - .params(); - - params.update_selector(Selector::new(selector)); - - let result = params - .try_instantiate() - .expect("Error from the Contracts pallet."); - - match result { - Ok(_) => None, - Err(e @ ink::LangError::CouldNotReadInput) => Some(e), - Err(_) => { - unimplemented!("No other `LangError` variants exist at the moment.") - } - } - } - - /// Attempt to instantiate a contract using the `CreateBuilder`. - /// - /// Since we can't use the `CreateBuilder` in a test environment directly we need - /// this wrapper to test things like crafting calls with invalid - /// selectors. - /// - /// We also wrap the output in an `Option` since we can't return a `Result` - /// directly from a contract message without erroring out ourselves. - #[ink(message)] - pub fn call_instantiate_fallible( - &mut self, - code_hash: Hash, - selector: [u8; 4], - init_value: bool, - ) -> Option< - Result< - Result, - ink::LangError, - >, - > { - let mut params = ConstructorsReturnValueRef::try_new(init_value) - .code_hash(code_hash) - .gas_limit(0) - .endowment(0) - .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) - .params(); - - params.update_selector(Selector::new(selector)); - - let lang_result = params - .try_instantiate() - .expect("Error from the Contracts pallet."); - - Some(lang_result.map(|contract_result| { - contract_result.map(|inner| ink::ToAccountId::to_account_id(&inner)) - })) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - ChainBackend, - ContractsBackend, - }; - use integration_flipper::{ - Flipper, - FlipperRef, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_invalid_message_selector_can_be_handled( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder_contract = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder_contract.call::(); - - let mut flipper_constructor = FlipperRef::new_default(); - let flipper = client - .instantiate("integration_flipper", &origin, &mut flipper_constructor) - .submit() - .await - .expect("instantiate `flipper` failed"); - let flipper_call = flipper.call::(); - - let flipper_get = flipper_call.get(); - let get_call_result = client.call(&origin, &flipper_get).dry_run().await?; - let initial_value = get_call_result.return_value(); - - let selector = ink::selector_bytes!("invalid_selector"); - let call = call_builder_call.call(flipper.account_id, selector); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Calling `call_builder::call` failed"); - - let flipper_result = call_result.return_value(); - - assert!(matches!( - flipper_result, - Some(ink::LangError::CouldNotReadInput) - )); - - let flipper_get = flipper_call.get(); - let get_call_result = client.call(&origin, &flipper_get).dry_run().await?; - let flipped_value = get_call_result.return_value(); - assert!(flipped_value == initial_value); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_invalid_message_selector_panics_on_invoke( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let mut flipper_constructor = FlipperRef::new_default(); - let flipper = client - .instantiate("integration_flipper", &origin, &mut flipper_constructor) - .submit() - .await - .expect("instantiate `flipper` failed"); - - // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method - // we expect this to panic. - let invalid_selector = [0x00, 0x00, 0x00, 0x00]; - let call = call_builder_call.invoke(flipper.account_id, invalid_selector); - let call_result = client.call(&origin, &call).dry_run().await; - - if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { - assert!( - dry_run - .debug_message - .contains("Cross-contract call failed with CouldNotReadInput"), - "Call execution failed for an unexpected reason." - ); - } else { - panic!("Call execution should've failed, but didn't."); - } - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_works_with_valid_selector( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("new"); - let init_value = true; - let call = - call_builder_call.call_instantiate(code_hash, selector, init_value); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Client failed to call `call_builder::call_instantiate`.") - .return_value(); - - assert!( - call_result.is_none(), - "Call using valid selector failed, when it should've succeeded." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_fails_with_invalid_selector( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("invalid_selector"); - let init_value = true; - let call = - call_builder_call.call_instantiate(code_hash, selector, init_value); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Client failed to call `call_builder::call_instantiate`.") - .return_value(); - - assert!( - matches!(call_result, Some(ink::LangError::CouldNotReadInput)), - "Call using invalid selector succeeded, when it should've failed." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_with_infallible_revert_constructor_encodes_ok< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("revert_new"); - let init_value = false; - let call = - call_builder_call.call_instantiate(code_hash, selector, init_value); - - let call_result = client.call(&origin, &call).dry_run().await; - - if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { - assert!(dry_run - .debug_message - .contains("The callee reverted, but did not encode an error in the output buffer."), - "Call execution failed for an unexpected reason."); - } else { - panic!("Call execution should've failed, but didn't."); - } - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_can_handle_fallible_constructor_success< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("try_new"); - let init_value = true; - let call = call_builder_call - .call_instantiate_fallible(code_hash, selector, init_value); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Calling `call_builder::call_instantiate_fallible` failed") - .return_value(); - - assert!( - matches!(call_result, Some(Ok(_))), - "Call to falliable constructor failed, when it should have succeeded." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_can_handle_fallible_constructor_error< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("try_new"); - let init_value = false; - let call = call_builder_call - .call_instantiate_fallible(code_hash, selector, init_value); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect("Calling `call_builder::call_instantiate_fallible` failed") - .return_value(); - - let contract_result = call_result - .unwrap() - .expect("Dispatching `constructors_return_value::try_new` failed."); - - assert!( - matches!( - contract_result, - Err(constructors_return_value::ConstructorError) - ), - "Got an unexpected error from the contract." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_with_fallible_revert_constructor_encodes_ok< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("try_revert_new"); - let init_value = true; - let call = call_builder_call - .call_instantiate_fallible(code_hash, selector, init_value); - let call_result = client.call(&origin, &call).dry_run().await; - - if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { - assert!(dry_run - .debug_message - .contains("The callee reverted, but did not encode an error in the output buffer."), - "Call execution failed for an unexpected reason."); - } else { - panic!("Call execution should've failed, but didn't."); - } - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_create_builder_with_fallible_revert_constructor_encodes_err< - Client: E2EBackend, - >( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) - .await; - - let mut constructor = CallBuilderTestRef::new(); - let call_builder = client - .instantiate("call_builder", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("constructors_return_value", &origin) - .submit() - .await - .expect("upload `constructors_return_value` failed") - .code_hash; - - let selector = ink::selector_bytes!("try_revert_new"); - let init_value = false; - let call = call_builder_call - .call_instantiate_fallible(code_hash, selector, init_value); - let call_result = client - .call(&origin, &call) - .submit() - .await - .expect( - "Client failed to call `call_builder::call_instantiate_fallible`.", - ) - .return_value(); - - assert!( - matches!(call_result, Some(Err(ink::LangError::CouldNotReadInput))), - "The callee manually encoded `CouldNotReadInput` to the output buffer, we should've - gotten that back." - ); - - Ok(()) - } - } -} diff --git a/integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml b/integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml deleted file mode 100644 index f2687c0315b..00000000000 --- a/integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "constructors_return_value" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs b/integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs deleted file mode 100644 index 76cd2bcfdcd..00000000000 --- a/integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs +++ /dev/null @@ -1,250 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::constructors_return_value::{ - ConstructorError, - ConstructorsReturnValue, - ConstructorsReturnValueRef, -}; - -#[ink::contract] -pub mod constructors_return_value { - #[ink(storage)] - pub struct ConstructorsReturnValue { - value: bool, - } - - #[derive(Debug)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct ConstructorError; - - impl ConstructorsReturnValue { - /// Infallible constructor - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { value: init_value } - } - - /// Fallible constructor - #[ink(constructor)] - pub fn try_new(succeed: bool) -> Result { - if succeed { - Ok(Self::new(true)) - } else { - Err(ConstructorError) - } - } - - /// A constructor which reverts and fills the output buffer with an erroneously - /// encoded return value. - #[ink(constructor)] - pub fn revert_new(_init_value: bool) -> Self { - ink::env::return_value::>( - ink::env::ReturnFlags::REVERT, - &Ok(AccountId::from([0u8; 32])), - ) - } - - /// A constructor which reverts and fills the output buffer with an erroneously - /// encoded return value. - #[ink(constructor)] - pub fn try_revert_new(init_value: bool) -> Result { - let value = if init_value { - Ok(Ok(AccountId::from([0u8; 32]))) - } else { - Err(ink::LangError::CouldNotReadInput) - }; - - ink::env::return_value::< - ink::ConstructorResult>, - >(ink::env::ReturnFlags::REVERT, &value) - } - - /// Returns the current value of the contract storage. - #[ink(message)] - pub fn get_value(&self) -> bool { - self.value - } - } - - #[cfg(test)] - mod tests { - use super::ConstructorsReturnValue as Contract; - use std::any::TypeId; - - #[test] - #[allow(clippy::assertions_on_constants)] - fn infallible_constructor_reflection() { - const ID: u32 = ::ink::selector_id!("new"); - - assert!( - !>::IS_RESULT, - ); - assert_eq!( - TypeId::of::< - >::Error, - >(), - TypeId::of::<&()>(), - ) - } - - #[test] - #[allow(clippy::assertions_on_constants)] - fn fallible_constructor_reflection() { - const ID: u32 = ::ink::selector_id!("try_new"); - - assert!( - >::IS_RESULT, - ); - assert_eq!( - TypeId::of::< - >::Error, - >(), - TypeId::of::(), - ) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_infallible_constructor( - mut client: Client, - ) -> E2EResult<()> { - let mut constructor = ConstructorsReturnValueRef::new(true); - let infallible_constructor_result = client - .instantiate( - "constructors_return_value", - &ink_e2e::alice(), - &mut constructor, - ) - .dry_run() - .await?; - - let decoded_result = infallible_constructor_result.constructor_result::<()>(); - assert!( - decoded_result.is_ok(), - "Constructor dispatch should have succeeded" - ); - - let mut constructor = ConstructorsReturnValueRef::new(true); - let success = client - .instantiate( - "constructors_return_value", - &ink_e2e::alice(), - &mut constructor, - ) - .submit() - .await - .is_ok(); - - assert!(success, "Contract created successfully"); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_fallible_constructor_succeed( - mut client: Client, - ) -> E2EResult<()> { - let mut constructor = ConstructorsReturnValueRef::try_new(true); - let result = client - .instantiate( - "constructors_return_value", - &ink_e2e::bob(), - &mut constructor, - ) - .dry_run() - .await?; - - let decoded_result = - result.constructor_result::>(); - - assert!( - decoded_result.is_ok(), - "Constructor dispatch should have succeeded" - ); - - assert!( - decoded_result.unwrap().is_ok(), - "Fallible constructor should have succeeded" - ); - - let mut constructor = ConstructorsReturnValueRef::try_new(true); - let contract = client - .instantiate( - "constructors_return_value", - &ink_e2e::bob(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let call = contract.call::(); - - let get = call.get_value(); - let value = client - .call(&ink_e2e::bob(), &get) - .dry_run() - .await? - .return_value(); - - assert_eq!( - true, value, - "Contract success should write to contract storage" - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_fallible_constructor_fails( - mut client: Client, - ) -> E2EResult<()> { - let mut constructor = ConstructorsReturnValueRef::try_new(false); - - let result = client - .instantiate( - "constructors_return_value", - &ink_e2e::charlie(), - &mut constructor, - ) - .dry_run() - .await?; - - let decoded_result = - result.constructor_result::>(); - - assert!( - decoded_result.is_ok(), - "Constructor dispatch should have succeeded" - ); - - assert!( - decoded_result.unwrap().is_err(), - "Fallible constructor should have failed" - ); - - let mut constructor = ConstructorsReturnValueRef::try_new(false); - let result = client - .instantiate( - "constructors_return_value", - &ink_e2e::charlie(), - &mut constructor, - ) - .submit() - .await; - - assert!( - matches!(result, Err(ink_e2e::Error::InstantiateExtrinsic(_))), - "Constructor should fail" - ); - - Ok(()) - } - } -} diff --git a/integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml b/integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml deleted file mode 100755 index 89fe2d62c42..00000000000 --- a/integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "contract_ref" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -integration_flipper = { path = "../integration-flipper", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "integration_flipper/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/contract-ref/lib.rs b/integration-tests/lang-err-integration-tests/contract-ref/lib.rs deleted file mode 100755 index 90336b1069c..00000000000 --- a/integration-tests/lang-err-integration-tests/contract-ref/lib.rs +++ /dev/null @@ -1,188 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod contract_ref { - use integration_flipper::FlipperRef; - - #[ink(storage)] - pub struct ContractRef { - flipper: FlipperRef, - } - - impl ContractRef { - #[ink(constructor)] - pub fn new(version: u32, flipper_code_hash: Hash) -> Self { - let salt = version.to_le_bytes(); - let flipper = FlipperRef::new_default() - .endowment(0) - .code_hash(flipper_code_hash) - .salt_bytes(salt) - .instantiate(); - - Self { flipper } - } - - #[ink(constructor)] - pub fn try_new(version: u32, flipper_code_hash: Hash, succeed: bool) -> Self { - let salt = version.to_le_bytes(); - let flipper = FlipperRef::try_new(succeed) - .endowment(0) - .code_hash(flipper_code_hash) - .salt_bytes(salt) - .instantiate() - .unwrap_or_else(|error| { - panic!( - "Received an error from the Flipper constructor while instantiating \ - Flipper {error:?}" - ) - }); - - Self { flipper } - } - - #[ink(message)] - pub fn flip(&mut self) { - self.flipper.flip(); - } - - #[ink(message)] - pub fn flip_check(&mut self) { - self.flipper - .try_flip() - .expect("The ink! codegen should've produced a valid call."); - } - - #[ink(message)] - pub fn get(&mut self) -> bool { - self.flipper.get() - } - - #[ink(message)] - pub fn get_check(&mut self) -> bool { - self.flipper - .try_get() - .expect("The ink! codegen should've produced a valid call.") - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_ref_can_flip_correctly( - mut client: Client, - ) -> E2EResult<()> { - let flipper_hash = client - .upload("integration_flipper", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `flipper` failed") - .code_hash; - - let mut constructor = ContractRefRef::new(0, flipper_hash); - let contract_ref = client - .instantiate("contract_ref", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract_ref.call::(); - - let get_check = call.get_check(); - let get_call_result = - client.call(&ink_e2e::alice(), &get_check).dry_run().await?; - - let initial_value = get_call_result.return_value(); - - let flip_check = call.flip_check(); - let flip_call_result = client - .call(&ink_e2e::alice(), &flip_check) - .submit() - .await - .expect("Calling `flip` failed"); - assert!( - flip_call_result.message_result().is_ok(), - "Messages now return a `Result`, which should be `Ok` here." - ); - - let get_call_result = - client.call(&ink_e2e::alice(), &get_check).dry_run().await?; - let flipped_value = get_call_result.return_value(); - assert!(flipped_value != initial_value); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_fallible_ref_can_be_instantiated( - mut client: Client, - ) -> E2EResult<()> { - let flipper_hash = client - .upload("integration_flipper", &ink_e2e::bob()) - .submit() - .await - .expect("uploading `flipper` failed") - .code_hash; - - let succeed = true; - let mut constructor = ContractRefRef::try_new(0, flipper_hash, succeed); - let contract_ref = client - .instantiate("contract_ref", &ink_e2e::bob(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract_ref.call::(); - - let get_check = call.get_check(); - let get_call_result = - client.call(&ink_e2e::bob(), &get_check).dry_run().await?; - let initial_value = get_call_result.return_value(); - - assert!(initial_value); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_fallible_ref_fails_to_be_instantiated( - mut client: Client, - ) -> E2EResult<()> { - let flipper_hash = client - .upload("integration_flipper", &ink_e2e::charlie()) - .submit() - .await - .expect("uploading `flipper` failed") - .code_hash; - - let succeed = false; - let mut constructor = ContractRefRef::try_new(0, flipper_hash, succeed); - let instantiate_result = client - .instantiate("contract_ref", &ink_e2e::charlie(), &mut constructor) - .submit() - .await; - - assert!( - instantiate_result.is_err(), - "Call execution should've failed, but didn't." - ); - - let contains_err_msg = match instantiate_result.unwrap_err() { - ink_e2e::Error::InstantiateDryRun(dry_run) => { - dry_run.debug_message.contains( - "Received an error from the Flipper constructor while instantiating Flipper FlipperError" - ) - } - _ => false, - }; - assert!( - contains_err_msg, - "Call execution failed for an unexpected reason." - ); - - Ok(()) - } - } -} diff --git a/integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml b/integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml deleted file mode 100644 index 0d1d08c1ab2..00000000000 --- a/integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "integration_flipper" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/integration-flipper/lib.rs b/integration-tests/lang-err-integration-tests/integration-flipper/lib.rs deleted file mode 100644 index 496a478578f..00000000000 --- a/integration-tests/lang-err-integration-tests/integration-flipper/lib.rs +++ /dev/null @@ -1,152 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::integration_flipper::{ - Flipper, - FlipperRef, -}; - -#[ink::contract] -pub mod integration_flipper { - #[ink(storage)] - pub struct Flipper { - value: bool, - } - - #[derive(Debug)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct FlipperError; - - impl Flipper { - /// Creates a new integration_flipper smart contract initialized with the given - /// value. - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { value: init_value } - } - - /// Creates a new integration_flipper smart contract initialized to `false`. - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - /// Attempts to create a new integration_flipper smart contract initialized with - /// the given value. - #[ink(constructor)] - pub fn try_new(succeed: bool) -> Result { - if succeed { - Ok(Self::new(true)) - } else { - Err(FlipperError) - } - } - - /// Flips the current value of the Flipper's boolean. - #[ink(message)] - pub fn flip(&mut self) { - self.value = !self.value; - } - - /// Returns the current value of the Flipper's boolean. - #[ink(message)] - pub fn get(&self) -> bool { - self.value - } - - /// Flips the current value of the Flipper's boolean. - /// - /// We should see the state being reverted here, no write should occur. - #[ink(message)] - #[allow(clippy::result_unit_err)] - pub fn err_flip(&mut self) -> Result<(), ()> { - self.flip(); - Err(()) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_can_flip_correctly( - mut client: Client, - ) -> E2EResult<()> { - let mut constructor = FlipperRef::new_default(); - let flipper = client - .instantiate("integration_flipper", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("Instantiate `integration_flipper` failed"); - let mut call = flipper.call::(); - - let get = call.get(); - let initial_value = client - .call(&ink_e2e::alice(), &get) - .dry_run() - .await? - .return_value(); - - let flip = call.flip(); - let flip_call_result = client - .call(&ink_e2e::alice(), &flip) - .submit() - .await - .expect("Calling `flip` failed"); - assert!( - flip_call_result.message_result().is_ok(), - "Messages now return a `Result`, which should be `Ok` here." - ); - - let flipped_value = client - .call(&ink_e2e::alice(), &get) - .dry_run() - .await? - .return_value(); - assert!(flipped_value != initial_value); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_message_error_reverts_state( - mut client: Client, - ) -> E2EResult<()> { - let mut constructor = FlipperRef::new_default(); - let flipper = client - .instantiate("integration_flipper", &ink_e2e::bob(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = flipper.call::(); - - let get = call.get(); - let initial_value = client - .call(&ink_e2e::bob(), &get) - .dry_run() - .await? - .return_value(); - - let err_flip = call.err_flip(); - let err_flip_call_result = - client.call(&ink_e2e::bob(), &err_flip).submit().await; - - assert!(matches!( - err_flip_call_result, - Err(ink_e2e::Error::CallExtrinsic(_)) - )); - - let flipped_value = client - .call(&ink_e2e::bob(), &get) - .dry_run() - .await? - .return_value(); - assert!(flipped_value == initial_value); - - Ok(()) - } - } -} diff --git a/integration-tests/lazyvec-integration-test/.gitignore b/integration-tests/lazyvec-integration-test/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/lazyvec-integration-test/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/lazyvec-integration-test/Cargo.toml b/integration-tests/lazyvec-integration-test/Cargo.toml deleted file mode 100755 index 19cadf29860..00000000000 --- a/integration-tests/lazyvec-integration-test/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "lazyvec-integration-tests" -version = "5.0.0-alpha" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/lazyvec-integration-test/lib.rs b/integration-tests/lazyvec-integration-test/lib.rs deleted file mode 100755 index 2e6fa938964..00000000000 --- a/integration-tests/lazyvec-integration-test/lib.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! A smart contract which demonstrates functionality of `lazyvec` functions. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod lazyvec_integration_tests { - use ink::{ - prelude::vec::Vec, - storage::StorageVec, - }; - - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct Proposal { - data: Vec, - until: BlockNumber, - approvals: u32, - min_approvals: u32, - } - - impl Proposal { - fn is_finished(&self) -> bool { - self.until < ink::env::block_number::() - } - } - - #[ink(storage)] - pub struct LazyVector { - proposals: StorageVec, - } - - impl LazyVector { - #[ink(constructor, payable)] - pub fn default() -> Self { - Self { - proposals: Default::default(), - } - } - - /// Checks whether given account is allowed to vote and didn't already - /// participate. - fn is_eligible(&self, _voter: AccountId) -> bool { - // ToDo: In production, the contract would actually verify eligible voters. - // For example, a merkle proof could be an efficient way to do this. - true - } - - /// Vote to approve the current proposal. - #[ink(message)] - pub fn approve(&mut self) { - assert!(self.is_eligible(self.env().caller())); - - if let Some(mut proposal) = self.proposals.pop() { - assert!(!proposal.is_finished()); - - proposal.approvals = proposal.approvals.saturating_add(1); - self.proposals.push(&proposal); - } - } - - /// Create a new proposal. - /// - /// Returns `None` if the current proposal is not yet finished. - #[ink(message)] - pub fn create_proposal( - &mut self, - data: Vec, - duration: BlockNumber, - min_approvals: u32, - ) -> Option { - let proposal_number = match self.proposals.peek() { - Some(last) if !last.is_finished() => return None, - _ => self.proposals.len(), - }; - - self.proposals.push(&Proposal { - data, - until: self.env().block_number().saturating_add(duration.min(6000)), - min_approvals, - approvals: 0, - }); - - Some(proposal_number) - } - - #[ink(message)] - pub fn get(&self, at: u32) -> Option { - self.proposals.get(at) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn create_and_vote( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = LazyVectorRef::default(); - let contract = client - .instantiate( - "lazyvec-integration-tests", - &ink_e2e::alice(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let create = call.create_proposal(vec![0x41], 5, 1); - let _ = client - .call(&ink_e2e::alice(), &create) - .submit() - .await - .expect("Calling `create_proposal` failed"); - - let approve = call.approve(); - let _ = client - .call(&ink_e2e::alice(), &approve) - .submit() - .await - .expect("Voting failed"); - let _ = client - .call(&ink_e2e::bob(), &approve) - .submit() - .await - .expect("Voting failed"); - - // then - let value = client - .call(&ink_e2e::alice(), &create) - .dry_run() - .await - .expect("create trapped when it shouldn't") - .return_value(); - assert_eq!(value, None); - - let value = client - .call(&ink_e2e::alice(), &call.get(0)) - .dry_run() - .await - .expect("get trapped when it shouldn't") - .return_value(); - assert_eq!(value.unwrap().approvals, 2); - - Ok(()) - } - } -} diff --git a/integration-tests/mapping-integration-tests/.cargo/config.toml b/integration-tests/mapping-integration-tests/.cargo/config.toml deleted file mode 100644 index fcfc9c88880..00000000000 --- a/integration-tests/mapping-integration-tests/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[env] -# Makes testing the fallible storage methods more efficient -INK_STATIC_BUFFER_SIZE = "256" diff --git a/integration-tests/mapping-integration-tests/.gitignore b/integration-tests/mapping-integration-tests/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/mapping-integration-tests/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/mapping-integration-tests/Cargo.toml b/integration-tests/mapping-integration-tests/Cargo.toml deleted file mode 100755 index 7778efa2afc..00000000000 --- a/integration-tests/mapping-integration-tests/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "mapping-integration-tests" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/mapping-integration-tests/lib.rs b/integration-tests/mapping-integration-tests/lib.rs deleted file mode 100755 index fd5c19c6f1f..00000000000 --- a/integration-tests/mapping-integration-tests/lib.rs +++ /dev/null @@ -1,418 +0,0 @@ -//! A smart contract which demonstrates functionality of `Mapping` functions. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod mapping_integration_tests { - use ink::{ - prelude::{ - string::String, - vec::Vec, - }, - storage::Mapping, - }; - - #[derive(Debug, PartialEq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum ContractError { - ValueTooLarge, - } - - /// A contract for testing `Mapping` functionality. - #[ink(storage)] - #[derive(Default)] - pub struct Mappings { - /// Mapping from owner to number of owned token. - balances: Mapping, - /// Mapping from owner to aliases. - names: Mapping>, - } - - impl Mappings { - /// Demonstrates the usage of `Mappings::default()` - /// - /// Creates an empty mapping between accounts and balances. - #[ink(constructor)] - pub fn new() -> Self { - let balances = Mapping::default(); - let names = Mapping::default(); - Self { balances, names } - } - - /// Demonstrates the usage of `Mapping::get()`. - /// - /// Returns the balance of a account, or `None` if the account is not in the - /// `Mapping`. - #[ink(message)] - pub fn get_balance(&self) -> Option { - let caller = Self::env().caller(); - self.balances.get(caller) - } - - /// Demonstrates the usage of `Mappings::insert()`. - /// - /// Assigns the value to a given account. - /// - /// Returns the size of the pre-existing balance at the specified key if any. - /// Returns `None` if the account was not previously in the `Mapping`. - #[ink(message)] - pub fn insert_balance(&mut self, value: Balance) -> Option { - let caller = Self::env().caller(); - self.balances.insert(caller, &value) - } - - /// Demonstrates the usage of `Mappings::size()`. - /// - /// Returns the size of the pre-existing balance at the specified key if any. - /// Returns `None` if the account was not previously in the `Mapping`. - #[ink(message)] - pub fn size_balance(&mut self) -> Option { - let caller = Self::env().caller(); - self.balances.size(caller) - } - - /// Demonstrates the usage of `Mapping::contains()`. - /// - /// Returns `true` if the account has any balance assigned to it. - #[ink(message)] - pub fn contains_balance(&self) -> bool { - let caller = Self::env().caller(); - self.balances.contains(caller) - } - - /// Demonstrates the usage of `Mappings::remove()`. - /// - /// Removes the balance entry for a given account. - #[ink(message)] - pub fn remove_balance(&mut self) { - let caller = Self::env().caller(); - self.balances.remove(caller); - } - - /// Demonstrates the usage of `Mappings::take()`. - /// - /// Returns the balance of a given account removing it from storage. - /// - /// Returns `None` if the account is not in the `Mapping`. - #[ink(message)] - pub fn take_balance(&mut self) -> Option { - let caller = Self::env().caller(); - self.balances.take(caller) - } - - /// Demonstrates the usage of `Mappings::try_take()` and `Mappings::try_insert()`. - /// - /// Adds a name of a given account. - /// - /// Returns `Ok(None)` if the account is not in the `Mapping`. - /// Returns `Ok(Some(_))` if the account was already in the `Mapping` - /// Returns `Err(_)` if the mapping value couldn't be encoded. - #[ink(message)] - pub fn try_insert_name(&mut self, name: String) -> Result<(), ContractError> { - let caller = Self::env().caller(); - let mut names = match self.names.try_take(caller) { - None => Vec::new(), - Some(value) => value.map_err(|_| ContractError::ValueTooLarge)?, - }; - - names.push(name); - - self.names - .try_insert(caller, &names) - .map_err(|_| ContractError::ValueTooLarge)?; - - Ok(()) - } - - /// Demonstrates the usage of `Mappings::try_get()`. - /// - /// Returns the name of a given account. - /// - /// Returns `Ok(None)` if the account is not in the `Mapping`. - /// Returns `Ok(Some(_))` if the account was already in the `Mapping` - /// Returns `Err(_)` if the mapping value couldn't be encoded. - #[ink(message)] - pub fn try_get_names(&mut self) -> Option, ContractError>> { - let caller = Self::env().caller(); - self.names - .try_get(caller) - .map(|result| result.map_err(|_| ContractError::ValueTooLarge)) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn insert_and_get_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = MappingsRef::new(); - let contract = client - .instantiate( - "mapping-integration-tests", - &ink_e2e::alice(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let insert = call.insert_balance(1_000); - let size = client - .call(&ink_e2e::alice(), &insert) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - // then - let get = call.get_balance(); - let balance = client - .call(&ink_e2e::alice(), &get) - .dry_run() - .await? - .return_value(); - - assert!(size.is_none()); - assert_eq!(balance, Some(1_000)); - - Ok(()) - } - - #[ink_e2e::test] - async fn insert_and_contains_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = MappingsRef::new(); - let contract = client - .instantiate( - "mapping-integration-tests", - &ink_e2e::bob(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let insert = call.insert_balance(1_000); - let _ = client - .call(&ink_e2e::bob(), &insert) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - // then - let contains = call.contains_balance(); - let is_there = client - .call(&ink_e2e::bob(), &contains) - .dry_run() - .await? - .return_value(); - - assert!(is_there); - - Ok(()) - } - - #[ink_e2e::test] - async fn reinsert_works(mut client: Client) -> E2EResult<()> { - // given - let mut constructor = MappingsRef::new(); - let contract = client - .instantiate( - "mapping-integration-tests", - &ink_e2e::charlie(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let first_insert = call.insert_balance(1_000); - let _ = client - .call(&ink_e2e::charlie(), &first_insert) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - let insert = call.insert_balance(10_000); - let size = client - .call(&ink_e2e::charlie(), &insert) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - // then - assert!(size.is_some()); - - let get = call.get_balance(); - let balance = client - .call(&ink_e2e::charlie(), &get) - .dry_run() - .await? - .return_value(); - - assert_eq!(balance, Some(10_000)); - - Ok(()) - } - - #[ink_e2e::test] - async fn insert_and_remove_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = MappingsRef::new(); - let contract = client - .instantiate( - "mapping-integration-tests", - &ink_e2e::dave(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let insert = call.insert_balance(3_000); - let _ = client - .call(&ink_e2e::dave(), &insert) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - let remove = call.remove_balance(); - let _ = client - .call(&ink_e2e::dave(), &remove) - .submit() - .await - .expect("Calling `remove_balance` failed"); - - // then - let get = call.get_balance(); - let balance = client - .call(&ink_e2e::dave(), &get) - .dry_run() - .await? - .return_value(); - - assert_eq!(balance, None); - - Ok(()) - } - - #[ink_e2e::test] - async fn insert_and_take_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = MappingsRef::new(); - let contract = client - .instantiate( - "mapping-integration-tests", - &ink_e2e::eve(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when - let insert = call.insert_balance(4_000); - let _ = client - .call(&ink_e2e::eve(), &insert) - .submit() - .await - .expect("Calling `insert_balance` failed") - .return_value(); - - let take = call.take_balance(); - let balance = client - .call(&ink_e2e::eve(), &take) - .submit() - .await - .expect("Calling `take_balance` failed") - .return_value(); - - // then - assert_eq!(balance, Some(4_000)); - - let contains = call.contains_balance(); - let is_there = client - .call(&ink_e2e::eve(), &contains) - .dry_run() - .await? - .return_value(); - - assert!(!is_there); - - Ok(()) - } - - #[ink_e2e::test] - async fn fallible_storage_methods_work( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = MappingsRef::new(); - let contract = client - .instantiate( - "mapping-integration-tests", - &ink_e2e::ferdie(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - // when the mapping value overgrows the buffer - let name = ink_e2e::ferdie().public_key().to_account_id().to_string(); - let insert = call.try_insert_name(name.clone()); - let mut names = Vec::new(); - while let Ok(_) = client.call(&ink_e2e::ferdie(), &insert).submit().await { - names.push(name.clone()) - } - - // then adding another one should fail gracefully - let expected_insert_result = client - .call(&ink_e2e::ferdie(), &insert) - .dry_run() - .await? - .return_value(); - let received_insert_result = - Err(crate::mapping_integration_tests::ContractError::ValueTooLarge); - assert_eq!(received_insert_result, expected_insert_result); - - // then there should be 4 entries (that's what fits into the 256kb buffer) - let received_mapping_value = client - .call(&ink_e2e::ferdie(), &call.try_get_names()) - .dry_run() - .await? - .return_value(); - let expected_mapping_value = Some(Ok(names)); - assert_eq!(received_mapping_value, expected_mapping_value); - - Ok(()) - } - } -} diff --git a/integration-tests/mother/.gitignore b/integration-tests/mother/.gitignore deleted file mode 100755 index d25982675a1..00000000000 --- a/integration-tests/mother/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock - -*~ -#*# \ No newline at end of file diff --git a/integration-tests/mother/Cargo.toml b/integration-tests/mother/Cargo.toml deleted file mode 100755 index 2c6696e9ced..00000000000 --- a/integration-tests/mother/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "mother" -description = "Mother of all contracts" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/mother/lib.rs b/integration-tests/mother/lib.rs deleted file mode 100755 index e70c6c69e21..00000000000 --- a/integration-tests/mother/lib.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! # The Mother of All Contracts -//! -//! This contract is intended to make use of all features that are observable -//! by off-chain tooling (for example user interfaces). -//! It doesn't do anything useful beyond serving off-chain tooling developers -//! with a contract to test their software against. -//! -//! Currently, this includes the following: -//! -//! 1. Use complex nested input and output types. This is done with the use case of a -//! data structure needed to store data of a candle auction. -//! 2. Make contract fail with `ContractTrapped`. -//! 3. Make contract fail with returning an `Error`. -//! 4. Perform debug printing from contract into the node's log. -//! 5. Use complex types in storage. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod mother { - use ink::prelude::{ - format, - string::{ - String, - ToString, - }, - vec::Vec, - }; - - use ink::storage::{ - Mapping, - StorageVec, - }; - - /// Struct for storing winning bids per bidding sample (a block). - /// Vector index corresponds to sample number. - /// Wrapping vector, just added for testing UI components. - #[derive(Default, PartialEq, Eq, Debug, Clone)] - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct Bids(Vec>>); - - /// Auction outline. - #[derive(PartialEq, Eq, Debug, Clone)] - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Outline { - NoWinner, - WinnerDetected, - PayoutCompleted, - } - - /// Auction statuses. - /// Logic inspired by - /// [Parachain Auction](https://github.com/paritytech/polkadot/blob/master/runtime/common/src/traits.rs#L160) - #[derive(PartialEq, Eq, Debug, Clone)] - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Status { - /// An auction has not started yet. - NotStarted, - /// We are in the starting period of the auction, collecting initial bids. - OpeningPeriod, - /// We are in the ending period of the auction, where we are taking snapshots of - /// the winning bids. Snapshots are taken currently on per-block basis, - /// but this logic could be later evolve to take snapshots of on - /// arbitrary length (in blocks). - EndingPeriod(BlockNumber), - /// Candle was blown. - Ended(Outline), - /// We have completed the bidding process and are waiting for the Random Function - /// to return some acceptable randomness to select the winner. The number - /// represents how many blocks we have been waiting. - RfDelay(BlockNumber), - } - - /// Struct for storing auction data. - #[derive(Debug, PartialEq, Eq, Clone)] - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct Auction { - /// Branded name of the auction event. - name: String, - /// Some hash identifying the auction subject. - subject: Hash, - /// Structure storing the bids being made. - bids: Bids, - /// Auction terms encoded as: - /// `[start_block, opening_period, closing_period]` - terms: [BlockNumber; 3], - /// Auction status. - status: Status, - /// Candle auction can have no winner. - /// If auction is finalized, that means that the winner is determined. - finalized: bool, - /// Just a vector for the UI tests. - vector: Vec, - } - - impl Default for Auction { - fn default() -> Auction { - Auction { - name: String::default(), - subject: Hash::default(), - bids: Bids::default(), - terms: <[BlockNumber; 3]>::default(), - status: Status::OpeningPeriod, - finalized: false, - vector: >::default(), - } - } - } - - /// Way to fail a contract execution. - #[derive(Debug, Eq, PartialEq, Clone)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Failure { - Revert(String), - Panic, - } - - /// Event emitted when an auction being echoed. - #[ink(event)] - pub struct AuctionEchoed { - auction: Auction, - } - - /// Storage of the contract. - #[ink(storage)] - #[derive(Default)] - pub struct Mother { - auction: Auction, - balances: Mapping, - log: StorageVec, - } - - impl Mother { - #[ink(constructor)] - pub fn new(auction: Auction) -> Self { - Self { - balances: Default::default(), - log: Default::default(), - auction, - } - } - - #[ink(constructor)] - pub fn new_default() -> Self { - Default::default() - } - - /// Demonstrates the ability to fail a constructor safely. - #[ink(constructor)] - pub fn failed_new(fail: bool) -> Result { - if fail { - Err(Failure::Revert("Reverting instantiation".to_string())) - } else { - Ok(Default::default()) - } - } - - /// Takes an auction data struct as input and returns it back. - #[ink(message)] - pub fn echo_auction(&mut self, auction: Auction) -> Auction { - self.env().emit_event(AuctionEchoed { - auction: auction.clone(), - }); - auction - } - - /// Fails contract execution in the required way. - #[ink(message)] - pub fn revert_or_trap(&mut self, fail: Option) -> Result<(), Failure> { - match fail { - Some(Failure::Revert(_)) => { - Err(Failure::Revert("Reverting on user demand!".to_string())) - } - Some(Failure::Panic) => { - panic!("Trapping on user demand!") - } - None => Ok(()), - } - } - - /// Prints the specified string into node's debug log. - #[ink(message)] - pub fn debug_log(&mut self, _message: String) { - ink::env::debug_println!("debug_log: {}", _message); - } - - /// Mutates the input string to return "Hello, { name }" - #[ink(message)] - pub fn mut_hello_world(&self, mut message: String) -> String { - message = format!("Hello, {}", message); - message - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn echo_auction_works() { - let auction = Auction::default(); - let mut contract = Mother::new_default(); - assert_eq!(contract.echo_auction(auction.clone()), auction); - } - - #[ink::test] - fn revert_works() { - let mut contract = Mother::default(); - assert_eq!( - contract.revert_or_trap(Some(Failure::Revert( - "Testing reverting on demand!".to_string() - ))), - Err(Failure::Revert("Reverting on user demand!".to_string())) - ); - contract - .revert_or_trap(None) - .expect("Contract unexpected failure!"); - } - - #[ink::test] - fn constructor_works_or_fails() { - let contract = Mother::failed_new(true); - assert!(contract.is_err()); - assert_eq!( - contract.err(), - Some(Failure::Revert("Reverting instantiation".to_string())) - ); - - let contract = Mother::failed_new(false); - assert!(contract.is_ok()); - } - - #[ink::test] - #[should_panic] - fn trap_works() { - let mut contract = Mother::default(); - let _ = contract.revert_or_trap(Some(Failure::Panic)); - } - - #[ink::test] - fn mut_works() { - let contract = Mother::default(); - let res = contract.mut_hello_world("Alice".to_string()); - assert_eq!("Hello, Alice", res) - } - } -} diff --git a/integration-tests/multi-contract-caller/.gitignore b/integration-tests/multi-contract-caller/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/multi-contract-caller/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/multi-contract-caller/.images/code-hashes.png b/integration-tests/multi-contract-caller/.images/code-hashes.png deleted file mode 100644 index db938ede981c6ff3ecc18b903319e45b6e68ed82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95399 zcmce;WmFtZ*EZU~9SEM_PJ+8jaDqDocY+6Z_h7+7aEIXT?!nzHxVy{XGklZW&+~rk ztn=$#>#Q@-%ydszb=B_O*S4z&Q&NybMIt}~00325N=z95{xO1X_^)B1M_}sshM{l& zn17W22msa5$j^qap!@HPrIh6Xz>^99{DT1C5qik~003NA0O05|0PrRQ0Iq#{%O`&5 z7w|^1l48KipP!%Yg>ld`0co+1s_siCt1jMH1Ixh0{tENiS+D3nZf^rOd{Ohc1MVGI53{+@$rd9@8A7tolSByyPAib#N2tix6HoJNW^FQI6P;C zMk)lc#}qKTstf9+0Mmv>lls}NM)dAmIPLf8w}yn`{CnwH0pfM}$DNVNSe5DdGS9tT z+nbrpUqj3q0MD z@;jDQbga$wdF)=9Vh%+2h=OQ5&H5Z;#GD_7Ie~X%lw=YF0*?1v57$-Re+Q`aVnQbu zJ8VLkZ~8F+BC$Vhabp^R`{?eK5FZa|aygFH@q4-lyM<{rVqmPT+^3dDfk^%A{O)qc z25!#@h0Jam*I$wils0w`5dn<~J?IgR1QAy5?as@iQdif8!^U&(OAz;&t!=HjT&n~9 zS#)Qk-NSm4px|0?uy|r3j(a+Z9^riXD}YRS+Ol3OQcUioS^m7u&UrLMT(NxiWoB#+ z^wM#U75(a8t)NgbRQB-T0g}^F2r~#Vh{U|B>Z`R{>^DhYtQW>oc`c>^Ap=0O+WRnO zn_5O@q>WzyUR+$a-E~_(*?IeP!2r(x(zyRksT>|36_eom?eWt^_JRbp_~qrrSMc{U zhc_EPh54nTdBIY}xW;+oGnV!4SpN!YKLshStgH<9S(ej18foVo5Y(IZ+iHQHj-I}> zWXDl!`4tf{k)~XMD%C50>FohxUhk6>Y{i85#4z!k$seBqrDD}7T&8|%N=^|kTf&T;ED zd)hqTJM^+kk$PV}JRsxlkEMbRL(dg6*upfY$LDVc*8~XXD;Y~^3>vPQSH14)mnf1( zY;1836l20E_fC5q53*+jqB%TwE=Dvez*kSCnpA%u-6Emmjdtk~9LC4H&h}kwY#VcN zgEkJo@v@knP;G5(<>Gj>yWUOv5LA3eaR1ov;|1#Pq{skW!qM9Kkx8XD5h^@G@k z*iYqR6)O3aE09KI+fDbTF}1eM@W9Bm`qz&aiLsfR`^SNywWQO1Qcy)%t+Hj5koouO zFURlXv|GM(0Yd8ur3)k;m}(RCi3JLU#{Ee#)ej|S&#ZNnAYJ5=mPbd^Y-~)rEpiNrF5FK?pfuQkpT*5Q^z_SX;}vtR znL5o|qs<4<=U87`U0qwf!DZRNt)?cCLDN69wsWz=$*q)?&^cxr3L@42eXM4?{5MqT zKuL`2XF|>&_O^D{A?~7Z$b;!kbj;Z3)y?ZnfYg<794v?DqYT50Sj*d>Oe9rYN z9Gov0(z*_L^jZ%PmBX?6EH!v5f>J- zf-$A(-Wt>X^gW&}R)NC*@$*&Mng?kl>gm6TuC)KD`t{`YKX4R(0esfUgaP(nD1S2@ zYKZYKJif;N#e(=Bgh%A{IHs2-4Twh&TT3UYn`t_8U=zz)&hcn$yWVN879^(bvShM=9~%&MJ1)BcIzFdhljg| zyW;20cD%vLT0gQ3lzs@=ZwNyt%ip3{mWU@!M>9HBlVqM;JbXa#>D+pWY6oUFn+ptc zdJwStnmyPB{Xg6J+oAZcqc%T1W~UL{@9$N#yd&c?<-pZ5dJYZ_`ub~Cn>}M=qpfw~ zyir`Ae*s?P3CV_1q?T6me6?S5Bn zI(tTEQe@8@2EwC`kBnG-`SR(1#*4^0v@R06>n*!tRrJc*pV2qilp4)^Y~qENCJKSX__T08^0oRrR`n4)yT@|%FGBoGc)st4^8X4Bp}q)u(E~mFaHlws;-;M&`@(q4Y6@b zmyq2peZQ|zBD3I25hrIoG?7J17|8n^c;r#wtVp@JEgc=e#z~tmQOl#JCQb<_EKzc` ztwgYpxFdaPkRAImnc@wSm8F%aNROu*X=bKA>(u@$kxfjHAZ3iy^X0w)>@Ugl_MTnG z%sTkVG@%FQOB0_BZ)I%-552gqB2Q~7fzb(%t{{7^Qt)zK{o#6^CqYInZP`z5S%ga8 zKZ2D^rY-YQv@FP@qrp`&wPY!zFUKd~)tTYrt3k82`!157P{Pp%a-@pdGEp#|8fs6= zl>|$HT{n=v4KIwnKAG0WBNreEBACwPSHn&~F~I;-KhYvw$G_#OgCr^hvd{`KGt;mX z^;CC?lU+WM`oRM0FY7P7wdlN2P~)1OmBeko@vEZX+)S4r5R(X+DVkjyu9cz?^nrMb zh$w%OMg;zWN5R8-%VXJVPnNuRbL&Ncarn~lTo&4Te}By!cIx;1kfdmqH;bQNpR;nc z_h_c2mC;nE{tv*!rE6~WJpq4aU}&^2WH}1&P{iAH`4A&zOG5jjh0C_p$N*t92reSs_y&2kJzrJk&ZeBq?+^@0Gqih&ge-zMg zeG%dl8gYjs$Wz{?Jj~SE##$npp!&<&O6Ez?9A#r`sgvo`=DNfm)}&0E1g2V z)YoSjqpeTq#J+}OM;|}M^yTLZtOPSjI667N_~*o8M8-$*zDe_YU*9&@ds0(yQbVQXLH@Ol0SWQu%p!<1qcqFu za4d>!F4j;zV@TCi;PgW8Y`KS^q;Hl!zbs|V2uJ>Dl09F^;yWcL>g8UOCTmLrISTb( zEmf2&_=YzW##pGr(cVmcJiZ3cY8l1Q;{Uqjc5u9Z4+i6%N-hKDd9Ld=k zpronle00)`Yq0v^aB)uY0d-hfMI8noI8jXvUgJ<_ekGH(<~woI*9N^;)W{dU8&i{$ zpUv&;9@S%gBX1t|qIt87c%#hB?F{tvY|Izx``_Td%}kD8(TSOcUQ6fYH#`ut2{-Va zfQN=onF7Py%rt}7O_2Tg?8WD0JM_GLwZ}M@l-$PmMGyG>yoifOi!a_QB?>-ec#sn^ zXE(P(1LEhxYDZED|EWm}9!9=gM5Y#Zwn$S>fd#btq=FU_qt|PmzQ#yNpA!y#iJY~1 z9Gg>Am(H!_3>~ob(U8v%*rAi~%MYdU@ZeWP6ZsBVOLyW8u zLM((W0{sk2bl`#WDmikme%l7buC^iuV$q0|$}{6>t_q`4i)ndFB^^(Z5^gdy?ffKF zJi7!RGaaHx?hB|C)dkmI!F-p)jjPr#SL!p5#piG|N1Msj2D7L{_i1f8468Q#TMj7+ zmf?j77sfL1D6Qq)H284%Nx zj8R)uPp@TupFJYL8~i{mb|9kJaoTe$Jq7J_qMzBrn4%1CE8qXWF$ulHt=ZqDE#@NOwAwO> zig~lI_7Al4TXt1POqJKtH{YCebG6TB;aY_sVP){kwIdqmNs_7Hk%K-K1Ubgap_(~w zY=0rW?qt{J zJ`yX%3rqeUqa3QRLI*ihdi;LiHGu`{8d~aGGg>wG;_~&Xsw;U5Ogjz(+8qH*6Rp`g zqaH8EwZ5@D=Y9hG{JJlStFVvAZe8S#pq@NST!iqi)(Nh<@;{=Cw0cgqr!bG3T?e|J?ACd7nw7ox$e86{jAYL3}K5nxeDYD5H=iP8f8NEg&oPTKR z(~pbE(Sw-)4x4xK$I^4tPrJW}?wI9O>0G?dWuV|F5E%pbD-@Qh9-ZaKev9|~{lxVC zJtBZJW(Q`_4%Nfs6ZIQ~2SZ^`OlG1&FS`Alcb?mz^t?Q?xEBb&(QQBc-lPK zdx+MWT%Q**^{$qe;5IGlbbwCsZd*LFzRCM-xAwS2l(gwtcPxJJTX`pBN+1`*Qy9-W!b=pgZnMgS5HO*6S81g?eiHG}`_awzDdIc;N`csuko%2A+hH zKllah-*S*dl;{!BSl08^!_R7C1m?W*Kk?*^Dg#wpH!uDC9URhxcor`+@G2EXSfbdu z2wmgu+4Kkdv5Aa|em%oraE2aj?k6e1TSo2(8Z6~+ggT960M;BWf7u~43DcwiS=ddq z8$d^2^ZRnz6S&Ld4Y`JU1t=}anJU$}5@7?yO(~BWC5zO#$1v|2g4C#~R0e;{2rqnj z|4UnNPF5rr0OFB~Sc6ZH-X|zbeXyU4fC~ys7-fGT0`6JnhZA$x-Ylxp{nTYNObXLN zQVVX!w{iTI5`j$&gdgt|DyPEg{iY2ziqYM+6wku9we8AQ=cM(GDt_fZ@m(GE*E0L* zBFgYy&n~^Bw%2bKnd4oJ^YCGU1f&EBKPv;G(jT<-t(6OKzqW)uf7XkX1|R2-?$8w| zeRO42QMiuPtetDu1+hwaQm_THDrcF>6i{igN3J`$RvDdpdbOny+a2*0E`aRZ4|+`C@8tQk-RQz>>m1Hyh?v ztMf@gYi9g_2+(AmT3A?Yi&NqC$U92wD_%5({t{-9D0&AcsB_vbx_l&4kt3z$;W@A-_oKI z^B0wBQ5wAme@{MrAEJK{JC@~>9Z??{BHR)tmN)B`o5U}o>0F|k4GUO7+R-#ZsI73~ z)-;|HIS*c3Jl0U}CozE#6nt3GisP?2>VB>e_37{zy)xU_Gfyl2zTvyLb;T&}+tzgB zv@v&vU}esu=JSwHj||9zc#4PzU**@2g`bK|s0pvL*0qdG`+3~Roj}yBaP36ou)52( zTmps9%M1I(jcJ0NjpLZGC3nha{^A!3e>pU1vZO ztXC#e&U1OF>VzhcTh!H)i2zWxW;tp#97iumC`LINPSXIwWHG|$8bk^3y-3O#@dP@f zH;u8aX|ym>@@(PQva1*fi$Q=tW3;DU4w-_7JVrhCIbr}5G!pNZReinu%<0Ry$(*%8KxO_{nQ7P`OgOcUM~BGZV20FN zGoI^pl(mpSDuVpGBc6V#Xt4qoiK7uYX|*r;WW%1{#vwJ=o_v4G`M#VTT$v;a!n2D%I&|%7u${d5S^!I){OeN#Lh)Rey^}-Nf)u8%^#`8a zvHW^6dOCpGUm&k*LhXg!z=%4a=UEgkV z8)ylht;ab)BkpMQ#F!xy01s;i4dB9QLtjuJpN~Rv=6jpw%Sbu#CHZFUT17I%3+@*+ z^XPAfEp;Qa%ii}8KhoB&eKtS+T}84O*cFD`c5c?^;K1Ss7bqHNmQALTt;$(CiHbgGeu=Q@-MJ#jP^eplNEXGBlydz=BVnr0}-;z33pA<}IhGaO4 zX0?N%zBg=~*&Dj5Lz8M!#F9G+GL zGvCkW^}%uO(|0zY%FGJZ8tavOox?k9x z{HgEMTKwhYi)p#i?&mdS!8j4^aNA_`yU!xzF^;^!|1iQq8b}(4=fXEV?b`UjBE%J5&eEzV@HN%r>9=tz z*z5*4t^=dI1Wd5~hfuUA5*{O{nuQI~lEpNIw#R)#h>p= z?Qgg^3sC^wlTk0fN}G;=ej<{BR{0&US8wm^N8k-H4m!{>;b9f$jO^AFTKDY9j3D@y z90rK{NJzNrRrnJFh^>(c4RU`2E#j@jrmhxlW1h#)a!0Tum-N=4wfOKYrY>tczB0lG z^30N=@IF_xc42H;>{^eFG1#J9*Z4Z9PkO!dl6Gow*ahv^#yvAUjE=`ECAX8!Rw(39 zm@mwrn>kQ<{s{RnvWO)g99!~|L8oSs!hw+>LKf#Q1{>DBL9Lu3BiiPkDNn7&K`=U9 z{u;ibVPWsMyr$qJ@2f$)p0#~-{S9sOVh}G2) zE=S<{9FR?kF zB-AXV25OoIoScItVoSY>-MIdXbWd{CMFhdAEA5773?7kRWnhFynp|P4N9V&4fVVIe z6BDEzdvU92>T*qZS?ynxdslNTh1|SIHJ0&nzZ!3yHQ-Xws%qmz%|?V;fJgm_r+9!W z#~i!;!el|^j*5nSwJ6-}+Aw=5Tv`}^CJ9*oG>hQgZfMjdb|ESz!vHVdsiSAChOw%vI-fNzA)#E2(6{__0@NmcM2dY#V@IVwSV%${A?b>)L zUgO>qw}!&=E$Bg^VXszyaVe$!V2iA|>bo5G7cCTml#hkLuVZA9q{z#)w;Ans%4>TW zVF*I$96ZzcafJJm^_ysp*|9_hT|`?9+~$@?yUz96ve|VfjUV&a%vGRK6xC(&j&{KC zLWikKz4kK2r?hHX;PZIFd<2Dr#y|dBUg%UX!fEDl+MX4DcvidJ8iGPfN>q8%gN*1@ zF7a^XOysh#;@BcGmYnP@KQQ$MeSLrWSHArG3e~GToG|0F*VLgGl8+8ND8tl}baF|} z7X6L$1!t_q1b`+!nf=b@wUnX>)K+@FOEFTen?4JX?ik;D(HowL0%tfj-t_h5hD})E zM(VbH&J$)JYZ z>QFGlBX`kPH4!s%9NwkZ@WP`4sa@re8CB};+kb!}7+_C*ZF}tbNy7`8#49TL=9}#9 zbC*_n7<161JHDSLhVm1+e&BnQ%oU{D}8vy_H#RE*wvP zdTc%SYWMbnkQEFhSJpOH#%X1I^6*SXdL)OC#B^^kuEr&9_}NvnVfWlE6Be!Z7}z<+P?pka;okT>Od2FRR@zlg+cS z_cx-5>`nbO2&7)b;Z~{*Zw;+T;Zw$bUP!#GgiBwv=rdZ?J84y8V+-3c9{2|^Kqv^q zj3BWZqLw`dD6K#Or3-kjW@p;^ zDp4$FE#H?`P<~ajI5VIziI)k~)9hlhX6H(fIqbfj>zA1toKHP7;3}?XT;89q*KMJy zZD=I@)q{5!Gl2r9q;j}08XV%y55-BL8dIkMg*5(pnN!cK^Hw)=I~!R2$cBdbyI zt#1otrQVp)`gx5Akdl(i6l@micYi$N*3i*e9xjp5&{(*|p@gc(x=KlTsY_Mkx#goL zy#(2G=hfjZG~7#edKZm!_3 zpSIYOmka+1lkj!5=iHAhB`jU>Bcu&Ju(omE%}#MEDJYB^_<=e7AkWFkc)&Y|>^rKx zogHD#*13tPa8!WS!zCalH)K&MG}x2L(|9@Bjl&+C5jjHRZTa}wTqh#0EY?r49~Bi8*_@DIU+Dh40RwD&F@^_d-DIKB#{q|N zD-IT%yP(k6OIo0X;>-D#*GRW15~#FO-+--ud3gv0@a2zvduDvPZKAGEJx*hp%sv{Z zInk5Z`6M#_P1p-Qs(XL(Wo(1Bcqtf?lS5@G!`U+-V=<%}C#*9o7r|&aaq%`#{x$CB zAGmj#M!D+9T)M+iy49-LQ8!(5L5##?zbNgmBm#Vhl<5h+5M^YkIdoWSc#msB2J_sNa?X)D6uOjysFexA*>35#7%5j~o?ZLP<0!}=6p)i^11ExiO=>~iNe!U| zBk={V`%7!JT*7=f;6L{BEONXfMhg|_fjucecbyp%$-u?NC-DM39e_g3A z*xnW$;&T@Fo``{w5n3?EmZ8j$V2`l@OAQ@-bCrNr!H-HxswO8Z%<3DBC}3GB2EY5c zQBzQ2I>h+qdpMk4w_}jwmwSlWh{9Wo=mPW1M9rXIYDTl4jM$(m-)V26fiOK)PM<*g z&0O(ZagI+5liG;Br_i}W8gv5+PERMo)VT$Lz*N{=!Ps~i$A%Mr77CNSZsXoHFp$`Hgc)RcZK`ZMCl<(%tPr!1H$&|)Q*KNMpdqJr}W}AoG zc02ZwE|}j#^H+McA>*=hjjzRFa@eu&vpX0?Pciz5gVEq&-mHv%ecbo7kuaBwq?Xl~ ziD(xzHS-quIcIrIm+Hsk+ghez-Jd-{v<2z9N*uf%zzwZj#5le_@jGWi^>g}^EF-TR zR}W3Hs9t|b9T6{+zz>$2UaC>MM)>v&*6|g{!00xg8p5^Xmc`O`vwFPVw${A!jMlU- zDjir#m$!oOHH@c=65|dM?zp^A_ur~B*r_VhhSlDx z{^400N&6}0!Ffy!JuEr_1Avx9-!NG$In@TpQ&*DO@xAwFj^{XQ)*mob>RwrNH6wnx z9nFa|N8{-HLE98VuFKIoJd>E6W?IEbm`%J70p}bFZW%A8GhXiHu9}eR<-J97q|@@s zjdY=k?)hmcz!(oRe`UMEG{aehFzfgy>*N@e(lU@zIc)Q@nkMzOWi8{uu_Z>R91iB! z>d?`SV!z}g*M9A`YkTq2^)`rIP+uswor6Yo3BG!M;QM6}M^Qp;h-Rvjg5D6cN+;Ya z{#X!=l*vQaR2nLM2fKI&30shFi_wzrmFEvxO<%&9*`t^8Zf~N42n)OV3q*G{BWz}We1Mvw+}qOwJ@oK6Y#e=4I9BG& zElXBf+QHeG%|SHm9Y)yt@-r*&d@;6=u_7-mEiEqo@GAhWH8n2H;S!jnXN~oJgl2G) ze*6&YO@$U&K7D#SL#=q`q15UK(heKaIJoWGxB>sTC5e)VOG_c4`fXFjaF$heoF+zH zxiU^$3jiH>*LgDTgl1M#>qga`g zkCMC+vWgwBh$warjLjcKgC&|1eDT}R3s29-@ZsnPS80#N+CD+u%6 zge}DxoqwLI9ZUjvU(fB^2;u3Kl`gNl9oR#|!g`m2wH+&Co7L-pL(kpV)|{H@sjkgz zqSt{e((kaSdIuDfKMeJv=G!XKBtRJpx$vl4*Y447_2Yx1E00eNw zUYRja#eE2UFX{y> z>A&O*1CNqYFova}#SA(5*pelDhwtZ0I?`rSQgXbDv7)E_*HZ07R05BvNIiMfi=rrT z+IJ?`x3?V*+aXY@QFDq%;Y7slp$UIG|)4 z=()olJP9}TxPXNdfsKXby!{BeUCW-O#0cBz2OYy9Kx-398wXRS&q*Y0k6m&Cw<+DF zq@!3|G|;U`Fz_yBGSFnFpf)2Tqp|TWiclabo&rPK+dI9nu`xfN66Xd}+zp33M#`jI zSw08u=4xn3HJYzY>6e^9ncN?m#2?O33Af`cpTHSO5`6wUr$d4;NdBvLH^O-5X~%ne zdj|)2n3$`xE3@sVT%6o`9o|DzQ&VGOsIm`3V(Eh)u(TLg(f`m;qW%6OweCNXgG1#& zhgXBcdzXz+vHGcDb!8=4ZZGP1K|r7#O6B4ey3G(NW*D4crWqvtVE=AS(z828ibAK+ zzgvW>=$LUR$~iX2?k*G;>2FQx&%6x-|MqKx#MT(FrzJ|dv3VB3is5;o9qqgTr3HI*EVt!GrT?jF*YdGSYHpiz3tzR$u#LLZRH?{QZH4LC9n#g-nDG`I@%xm zhnJ^hKFNszuVr}rR>m+bPKtsp1;t(QaA+nP{vU3ccUXJ-yK}R%zxh5uU(hr=Z$|%q z8jQd2WSJ68u*gR}Jv}wk{PObhq{chr7hj55{3WiR^mc+0(2gfDBVLC}*?b=2w|MkQ z7K3s?P*G8#G!W%t)qG{-U|3+|@?MoLxX);DX=!P3akE zMC!@hw^Z+EQSr$Ckp7AYwZ&yL-yOi@LWy~T;$$&(+T{=)v_Q%~{I!%xI+|`gL;);3 zs%*hRKi8RHxxc)_)2`B2H(XJV_c-WTvGBF5>lNjQKQLVgyh;H`ft|) zT5N3WF02?Ivz1oHN2kGI|0O#f42j&-TjdwrLd5=u%dUu%FJo$wqg?zH`QOw2Kj1_{ zS@C~qiht(z|7W*Kt<9M@Uz~Y^CGwSMG`}6dC_%Z23EKIpU+T3PQ2!2Twy*`0$^u5y z8YfuB=R^h$WPk;m0L4xi7Uh3wnm$E*8E1ITy!}!K**}$NoOw}4VE!^hLB(f5(pUug zg*l-iB7H}pf4S9<>TcZ>Z1@Pi!sw`Ba@`{TE z6D7`};Tjx(78)4yFVXc#sHnX|9Kq{J$nW|4{qyq(A(Z*;X*IGv5ZO>$4evj)V5_RC ze(O1GV`D=VN%9H+p+@a9>7G2mCo!fgdjIy@NtVg)UYegTT=%}43qAL0YJD3s&6nk^ zucR5&_w}zRNSKbu@i%nr;TCidWG3ha6l zF81&$cwi!3RzaQ|CwJ;V($$VyP^i_nXFB*AL0qg#t8C@#*L5hdT1gWHfQtRoFF?o$ zpOiT|?&%KjmtxSmIyt(inp9l#K?c~)%{m`82Y}DE{eET-%5EAO^s=%{q2=>mpyJq$ zi$f<8c#jhSvr$YLkvN)`D|^wE;fG18?|Hr2okIq^Mng#!uqnxk=8hhD8{P|DnQ$WD zoLATOJX99!u85dD{u&K!?Q%j$Hj!~+XsCbskH$RC26m*?8J->$;xJY<*5*%r8CUpj z*H+WjGZvK$nlELZ$l~l-oQhE>oWgg8YD3kJpZF?NE=^+}H{r+;LKc@79e-QX1sfTf z;qb7W#JQ~#usgah2fv(OD^f)fTG{p&&R7J%JqvmsqY^}|&FLmzj!{M8pPn8X8rn+a zP9>v+ltx^CU95%NU52U>l=sZ-k0(R=LeEjd!mC)gbCrv6_RMl6QI@af%%NOxMQd>i zvizf^#J9w2USLk^^k_^2gU+4a-dETALO1aYIxWtF8(rYqZ}SyeqU;{9Q(rh7n{{*N zZnk`z`PY^>t#tKPNsRiW6y(x5ssmNPYXYr)ki)`(0&yjp_{;5np9=`W)7@$78)8hi z>%G3@WFN1)j+eD3OlrA2mo2Y<(&YRqe!tEh=n3@Y3LDsct^(b4=frYORJ_L9kR*#K zP)xnJ&hleT8NH}_0}L|LJf9`4_Zo#IB;-doN>=#+z|IbC+e6pDai)&6^T4C|U1nKp zGoYlV0tYPE_33rEme-VMzr*@e6c1(mFL1XBQ^h4Q>)JmYfz%O|Xw}aB9$x`%kgEtB zGc)zX#2$F@Uj5F8 z<7lQ<0|!4x@9LcC&X2C73Vl9Yr}sRRr^kvq68&B;Gt5{>lz^(bdP{4I;N1-yMi_<0 zpx^W9#OO)-#g8ATR_d(ykx_*6LN8Ym3Ca&9^L^PdF}-hyfj3FXK~b2XU{l$oKHYE4 z*tiSp7<>E2t*&FKelK8+MF{@Jcm|(nDEj#-_{x2y-fG^&%q$@uOQ@{Ps(uM{yrAFY z{;^zL2r=rCY&5apL6-CN!IEqI|NKX~`5f+1WwQKGB z#5Q7(2>74pY9DjUejA_(KZ(U=`X9jF-X4HbLX}CPB^k&QhCq$$>c%QF87HlA{ZSxi zr)`I3ETrHuGt62j`Y`LYtLtb$)A5x$5L??!IVy6&Xj1lAF*2B6L$y=Y3%0Ze?|F6+%f4auNBV+->wEW+t2H3MJ1_~UMHIi)nor1Q@S4t-rAU4! z@;PCD8V$#0?tH#_IJhR%_iS#HmpmrsbGveXE=*+9yMDe7fPvU`UNfnq2fgvs{39rC zzNGtiZ|cREbQqXsOVtM&M zmnmtPPHuR3{JkItGAhO-r6_`UNA0B=+oDvGI7yO~ryedf^;XZb7W>{r>XcD`Cp!kRm`!F9*omxf{}4Lp07rPz&A5Oe6DqkkxDeb zAD_;t6*DsV+|cHsw%GF<$xW@aQCQIZ&*p`!sHS>YK2}qt_WEiEi3eagh`ji* zfxf=9vwLW36d0IG7KT?`k-fRwxRz4CQ;Z*xD1El-MiwVsp13eJ-5@QzFHrCP_ANOv zzpM8Yd#O2tc7x4+MPhjjUn^&^D%Be#9q_TIHi^d{-_Q4P7ne_`wJ!o!px@)>=HmDL z5q&5+DeGJE!}!Dm$UUAwMDQDt=p5ghHHu~thl}5rfdk*+{G$?fw_aTrMv}_rE0OiU z@8Mzv=zoUutPaX;C%$}7$(VsxJ3cfOFlOXJ4%0GN-XkU1IHR(kLy3V1+`D@;=*gAN zVX)$9F+bAMSAHYHlPGKTnec;02o(7wfUkG$!XhEk(c$ZLccTArQj3p|j{0Z0r;10#298 zWsvf)-?B;HzSq-d`g-=wC?4DGfYF6M_&i2Ju(-J!hvKNwQBXlwS6kN)jwxk%WhamI zR!?XrcGlLG%&a4cOgSZzvG&ObBuU;!@N>xP{7Rr>bRk^C&VZ3W;^r5C6*c655=HfyW>#n&9Z;+jzU*KC> zhWk|~jj1R(fP#ij%Hu4x+!sXlHfsRR|1gf)Y;8=vWR}On1GhpqO~8jPOe~etM#ja) zMn=0uCg0V*3K#ERlAUj>dKp>>P7umuW{ZVP$ zF36RBo3D@(9#3Du1|lL`Yv8AwMip)CoTpI}HZH$9kLM$*i*(9d4@sWFnZ7#dD^u$EhgNy9&D;a_)Ug!!6ZvAx*%bh{)HSf~7 zk%EZ~p)7}|+i#maSqAN%V=XR^xHpc?IdS=oo}i=sOpk)Rx}Ev*CfOurvXvhGT-6@wQJ?xw^~+KrFJjpX^iWBS*d1|?akTM*>hKL@b^B~AZ{ES zoc-L13<0l-?Ci|3y`Z3fDm~8@gRl2Hs|*9DWKYMRb{7n8PgkFAR1|H%;5*y0wYeQN zbyam$RS5}+I@CDbbUmm#0`KV9YHX6RaBw8@mE&SDTI2b32$!K=%sIp^xFJ4CyUBj@`GJtzrp0OhdzcH4_3eUz-|6r}QuEePJ1%{`*$RoE$r~V&M9})?!VtLFI=1Vhrdr_S(A3dh($unfIARBi%1RPt zOc`)$OA%=pnW?$@OjycHaU z+`cU`6ovV`TSE!FdxMlzuB`*Dd{3OcWa@RqD-^chKRYS9gP)uRDl`lrw$)6wl|WaGMzB%KX-yBkUr zFIEkQ#E+IS08oFj-lZN2&Zqf71H1It!;9B@>U9rcJ5Xdu6|!5?Fz{|RTWzVUbInR2 zVspQ+s_eMEl4Z_6V05~i&c~!y1cMhE%riaAmK$syM;89RT3egpg!eM|R$C)oT^xKJ zdP3r7XVqu!&X#eBh@Q9abSN-}zGuLb=MI`&MMk0^NFo3b&C7vvKNAy^ zGIv)~Q7{;J>>&BT&(-?HaOAEN@_hZZFT&GNk+$>a#!U+QNFwvP+h;#fQRAffQ~j%0 zl~^(|vSnLFGB(}ZsrZB>aj_s$e&-_4%D2Ig5Q$vw135f4lh+9{QDM!Kab|2bOLcB* zzE%rot3uEBxwdcGl95@L-A?WwOPT$^hlPdeW!Bu9lrbGIkf*O-pP?F7VSt8!M*MuU zt=MsYat&R*%fZdp-0jl1_elIh>f3x^)=BSe|WC*xH z=?lIDJdW`do6f~XA!*0#n>$*@z&8>fS{tJhJ_qEU7sNVl-Exwt9alwZ9!L#Kgqp>#{8Y zeit;Uv|i<~eauhdwm6+HFxqG5_pFestRc{P)J4I}%DOl*Gt=GOHg_|4F_@m7POniR z5hPi+s$nALuI!k!uK9+5phStr(b2I*??a+`qMgkW7BS}aY9eDfJQ6O_t4h12S&A>d zPZ^ENIzIh3_}JLH2EL%qhU^n4YhMUHjunb1nM8cJxce#W=4A$*YtV%^J&B+$g!59) zqdBXEdhV9Hxl#=lB9YgimbK0_RV_76z5egr)VPfYSSo* z>xl}1my@L#OHZhsmNbAU`VH5pS%F?u>9B3$>YxF=0D|_5Tyk8}-CihZV`_4G>b#{3 zlqfZnE0nQx)$8vACh)b}}Z{X@B&= z{OL9`uQsP`YjBGEdVk`{ZK9%NxTHftUql%;Of39$bY%0JTp`1fPR#qC2Kk)EW*igTIix>=D6H6X~`gFJM*P6kPYn~L%<+pOWKP*?rpokv5*%RV( zu_;N`<6!kxee~;-#x!>6;&iv1I%>qr}y4#t-ZRS>^!?k@>M3)ct%N*f8fqk}ib2`kdzw56}Bq?Y~NvO>Q)_^eV*@f{TKvtv{rtq&+-6o9YgJ9Ix)| z;?D{;WgX6*TMP~kG7d5d$ZXcOJ*X9P?1J0_>1n!jRjPjwF`+Vn^4)gi#0wx~?2QdLrEZJ! zdhQ!0M(xU+2{Nvh&e2}`n{l&e?JwQk-4c(l!G{$#bDItlA)T3>u7xFB@TnE5(0==1 zx|o-sZlllqZnZlCU(m6>E}||j7b{>OYjO9lIAzQnbg%W(lPBNIw$1VqdiwehAjHIn zL?~+tdU}sbi;rrh8WmqR2Y>(00{d<+r6f}SYgIBTY8fw#l7~W+9NoxdQ$)m{nA=go z#@dAlaQ-MT<+97cKd}*hV*QOu!aT) z-?*NN*>=n)h!^Err!BctjJZEypIJWhmK_LaoKgYMfGc1Q){MjlO>U ztfl2oCHZ66_uth2)-s!baI#u4MNH_~TS42cFB=Obo9gh{3Qy>#PoE5wz>zmvV=)E2 z(v4m3i*LCbl_vH&8v!lF+#r}P1`MDa7tsziW^k>In0Xuydf_FMFW|?IyLzL(2VVs5 zo9j)rXWh{k0ksk(@Fbo*B9-3%lTFx+%SUI`ffV?T)WT@L2mKvH0g z0O|-dT%rb#>q?g;H!9AHUKj1!*f^I>98=Kc;pn+8Pr+v3Hg>dBX9g6Xh>~VzXbv|m z;K|1Htj*QlpAYicpyILrIh#fnkpK4#uC>_Vk&_5boww?Qm;BW_LXZL17H>QcT9~-)1OOhG0aUJ0*#GnHk8hQZ%{zMtE4<9~6FV7Damln3#9 z4TFx8i$haWtA@+vDB~mWWC$i|Ca+$&=R|`1bex?X8y|lT>9~)qt>*_v+@~02<*-4l zSAHO2oNYa2!C`!kfWXJgBly?X^@o;++Zzeie5 zGB`9aFf?%3l)a~?@TRH*U2<}v2@P#I3T?rgIVdtA{Sge@gZ9&QJDP%!aqsWe(FzjN zmOPKgTDc0X=hv9ykE0C)gVIda^=`Xry4Fmnh*SD8+d0;CC3oMy?{Zl;*B`u)Q}0sT z`=9OmN5#b!b(azhlYPqWq9%L-q1URW=AOQqm|K$sxL1nf-PLhz)nFRm=FZ9u=$dQl zWG^koBDj zxZ=KrESlI=8g_S7ne)P7^=;UjaPo(IUO*_ev^@sk%A{Lda%{=*86_Hj*HXk zW~&|5vcxKsvE7jMUv_tyveqLezTbXx{$|Om&Zs|e1Qdsq1Gbhf($XOe+O;4-Fo8cj zh59i-HZR6~v{LZ?j14cf)@fG_^my4w^-WDRZ+`MBSD*?1`Ed~Hn6E%h&0e&v`}WoO ziyAG4gg(GK12c%Lw)+hjc!ZCGg8W{*c$yh0Ov1;<69KxXwLejH)}8k)p3u3P=$M$t zTbo1BkM69m<6<~`bA?-(nMo=u_w{kD+g~>ww$!aRfzeGq+oQkVO7@x@@!IG590uok z&d<*Q2rad=zI6(|?+#ftxjZz1!ln6+KMuSJI$j=)rOkBj2CF$@=*6C($Ca&tL9{T9 zi?e+iuTxP~)tG;R9?S7OKV)yZ;_GxIN!!CoU)$pYEG1f)|AYPI!RPzTVo)op!FBHk zxE@`0)_TAe=j7x~PLB6tA>!l1GJJlbk@@rS@udrT<}n4YZ|Zq1Tyzrydgb%PBCCK5 z(Xs33@GoDN!YbB*oHac}+;pB1NRt8N=ZF{((6B0VCQP&gjI+Fkn8af^xPEJl$ zAATBF?OR$R_@feV8omk=o&gZ!1t-?kRnV#>=Pr0TA+O^WfK7s>gK03eeA*tz3uCYl z*OBl!V-pfC-LGAy4S5Tx7>e_1Hc(iSjE-v03 zN-Si3GhWD9TT4%=oTHX~^QP_?my&*8wh2B63%^K3(M&Onh#U$V`+mL8#4nAJm8W zvZ;4~o`l0|^Yt$aJ1C74QfvKHJpig@QaLBOX2l9s;^X1~ zT6VbD0-bYa*=Hc+Op@D?>?%{5?Jom`_ zQGuz_q^Hq5!D911n zb1RFApzHIs2cka$FlH`^|8B1CtE#Cz91S^o+BOpf&1bfj8*D#Ck3)gDIE1Emyu7+b zCoTY5oJ>Iu&)MZyutQn&uyUtDSKnKo-*k)hFZNP z`DcMPp5GNtG&4Nxx}oD(yxl`%);Q$IYqk8{?b)YKm{LXtN+&>6?Wis!7am?^EaTwd zFlIW=)8N(}o^EPsPHAk%oif9gk8p*WF=m>Jg_SRt%0O+|kWyJylf>iD*i=jxCT3%9 zEhQrr9^N3)kH^YwJ|2k1$vH_HMa?Llu z)IYyO&$C}mj|a0D@811A6#9^f`3ikf~M(ELz;==rsRza{Xb(Pg1 z+<9%UB;4Fyuz22#_keX*n~Y~oYR)J>x;j>t9bkCDZ89liV) z!NI{77lQ_sXjrpXt8iBXV&)QvetgSxZUvDLjSD)2}wA^n{@@eBE zAl!mq`0Qs|%Pj^vkmHT61+5~8ik`SO-}rX{)NirgsM&WiFRW~B9g9G~3)pKgrPx?l zK<{tDK>eka2vb*6+aX>75N1zI*WApE`FL{UjsNM$NZp|!?gCWg>ZkrEgt?j7Xy7JM zv=fs*go%ZPfzMKblnon|PlFZr{jgir(Me50Hum)N+?&`ve*GH4%uMgtsoV$oTg)W* z!pGmgOTO)@D5iUMvTGx?j`N9+ukU^02${5WU+e8M73seMCW2UKa<=y*qU~XDV8Et~ zKZy1o0I5x14hXo^6a4cw+Qq{+HrD0SdEFFL$RQn6iv`93tK5m^V=GNyQ-W zRl#trVl_XIO%)2|@_fm*c|0iE%{~qs8o#?a1fb$Sg=n{46Dt}#?(+?sw}}<&1yIe& z)m}1UsX#Y>UKDwn*ay57g#Advys{ki70*<_?oWp$$EPJZO>I4up>*iqin1?Tv zximri^w_@`2lQ)Ns}K_xD( zR{}4Faoh3x3HJ;!nZlT*k&wj21M2I4!0hbeR5{uMP@J9ZrH1O^(pC8ZP+{TGSj*4&WtFFV!z!K3MKeNLQ#!Nl6jz5eA+;fAO-uvC$Rk_Bp&IIVE~$ zcUOR#c4zh+ze5>OF$ztxhi1xVvA3`9^8A8Gz`WxeRu`!G?CuT>Wdv?gH^wG{EYP3K zA|OB}zwwKJ-NjB#WhHBkmVo!c zo+6nzmkY zad%KU>pLpn^qig@DHN#yDF-+xa?pGFamPeR^)=l@0W9XA?C`g)$^W9vS&eUUulpq`NdH$FKo7A&vHquKZjk!(sevZE2az<7p0EmsBy6myd|8 zKGU|d3r9gA6GyJAu3ociz+u!iH^-JE5Gt*0Ev>99vRXy}0nyap4s}ZtAWn@S!~qT!*Z#Ki%{wV}ab-q4pY*@4D67tu}%52A?YISfJ%=+JX5V>N8W zray|3g$kEM0_r z%gM=}tXq;%5V%Dhos_h`w?cqNAfsaviZ1{L@$W9WIkW0KMMWoIFx`;g;AZE0F$vqq zn3yqnp+_wR6=cZF49{W9>Bf)OU+WI1I}KT_tSnIod6Hvd5Fo%~xICp`ka!=0}XdwBjerpeWVZ21>dYvN9uW6YZ<^-q+`svyHqEQ*-OZt6u4YQbv)Uy_qJxy6J_zlp&$( zKU3Y>bx~@(G4D_U*uYs6lJGUi;JHueoJ$}oWw^RRMjPk?5@wdK zCxn2^#FE&5*!n0*jsE0=weCZcO&g2(TA;&bc356jbIILs)K^F(3?`mC)*VXZwta$C zC^M<=XK89$@1lXvq}R&oGDvUGxV^T0dU*=IZdZresw%6Vyr<%du<3%an*CuDsR0-|tGRfQzHv=$2K{xqa1?PjsPvqMjg=2@! zH9_1R;o&F%R@^$q=%AoPdyBWg2T2*b<$1K0D{X19QZf8-z+GGSFVQ7LyFU)v>Us&L z{(1)(UwHjmRfxi?9Q069QcSdXQ-_ZzoNsyyM z9J$Ywj5qF~vZ`^aA3uKVFhNDVENS9tL@EIh*jq#^k9uh_W#j7Z%8vB>#bqOM;lvKG zh;0}4eFA_pfYl5ov)tS7P0Y_XZ$Qs```W|A@~J2&b}!{OQz<+LO4Y!DljUmOlsiyzyfh?YziY5 z8vOMut}3flRS` z9Z{5YtY>UL9erG{ zZ_MkO=*(>)7#79 zEqPNpojaI;sjQH26rfx+wX_m-Wk?$yb8}S+&Bo(T(P5py5BViNX5civb02-?FZyo^ z0HX;SYI{7M0{&u~1E~WBte}PqClb3Xi3C?!4n?^rd0GG0DV?Xm)y>cG$CXu76*NH zb$7SaXp2wOP|!FVW76ia4FAd8=Kq3v!jsZ#GFPb(^2NlY$$DVtY%U3UN&FU3)I9EH zB0)Dp(QMzU+tfg7xqJpPO#c>dzy{hK(!Kf`S=|hqb9dz}X8^|D-=p({^z9AwZ2YzVBvtKLo1qloK~Pvccy z8qQFm-W*I4o#5-(-_QDlv@vjgdMe;>p<`qer$QU@iWqQ+1EDVnNv+a_hsC6 zxZc!=F`(_%Vb%m8BZ_9JsSb9{HvSh5VB!Wi?Wd&?by83epyZbqPz$vUkByPh@pb?H z{o9o5?;tZ5*HrF&$G^j7R{=UPbkXdQ%Vq%e}y!~G=wCJf9VD{d^bXj>VP9Iyn zw{VHjaVgQIZA^?-D-KahJE=nPS^DSuk;!tqqqX3Sw6qt*%swKdPOE-I=s~e2uGf95 z4#ma5wU9opI=OT6*lAhqv*$hViv)JqFxc*R4~BY~`^KugAXTgQ;~f-QwJd7rP!(m- z`%OQy&}_R=?)^sBL9wF*eH{AhF&7HM zetTk2=s1=37$^kD-k8pvWfm;S4xr#SR-2%tWwvVud|ZA&(*ylI9cQ74lA4y+$rlj0}$q-dGDIpQhymbQ!3}hm_&*f=4ig3zL`sh=k>`hc9|8T0%_=x zEC1)m4_V%^BD2|3PmDGckG$?#15sB<-I_+dn;h`S;r)RjkxRrv+DFW?i5}Ps)(@iu8JgJ|{`DzY3$Y<-B1fpxFiJ$riB6bpHo%9yaw_Wl*}5mn>I3lV+& zD5XO3F2votV_I68it6g>a?^$~3CbA2gE-O|Z>}}e^MX$Pesy&Cfy|18D1WHk~$ zdcH;UyR}n1bS|Y zyx7?}bwi|GbAq1=5QyabxQr!IPtw!`HGCSoT{EoM;$|N?NW|PoAB~7bp?G8~;_^*DISaAL`ag$64KN!ejG*hDEjZIAWoJKpU&ycfyEu1t$d3YM= z(zNgh9STdTL;F@=UZGyOTzcnC^$rfexb;0$L`0;tk^wl;zpV@vjE&Kt4{C5V>&*#r zwa%{=NF(2k)D1x!5!Meet65BixT0Vu?~U14geB)(Zdrvqk=IJl7L z8R!F$absaIAn3sPjUHqOFfb~)l#SzSX&k0K4LM> z5nfZ{Wpc7(oo1ji~45~TCiEk)Kz3gKXi;f3x zzXzOHj#j_^n*IBeGck9x`ZVjTr-s7Ml0f|Yc zfiY_F(P(NuRLJ=1i@XaLCyWAHI=HX*E2AFHE59^rM!NX^C5Iyoxrcib46YL05%RZt zv)(FG_`~y|ZP#%1C%{V3<6gOMi~ZTBr`a$4fqb=qBRRVABt+d>&FM^rVTS>f%!_g$ z_o}F{8%!VJD(4SxodNm-D9)HZ?_4e90Vc4`^4=4;MFWYwngxJR8@j_&-+M)jo3d*^ zURhj}Y_pljWRI8{8Lex3FR7{NqCkYB$|Z;Sz2-I81obFmurRUrXIy;h?SmU^3o8oE z&H~XsA)qQ1PB=N88gCLcTMVEE!8|Hh!X>V@RiNd14nWCuGr$(D4X-aESvt_ zbb0yPAQAg} zxS*%mVEaf`q2?ZmmaVBKn<5CoBG?h;)k%lzM`FhffDpJxBH%EcH@6O z*=yMHL)s9mIx)kJk!-f!IvQCusBl$K83(V%$HPM*;GbWF#_$$a7SU2Of0^vZCE(Pi z#sqr5bRH<^=N?;Dml~>!JsPiljWdsSYIbF1OK&22l7^j?ZyYZ7=Gtr@nq3wbb0X=+ z*jNWe^%4bZa;E_c5T4F0VBawekULHe4)2>zb#P+mox8j30PwwAw}IDl#}JgP!M(jp zkJEYh1Z^=`KOG$+=`fAf*8w{1=YE+0fILJ*-rFxqBCZ-Du3*eET7rB%E)GJJCWY!fE;2+(4GauS3RIO8H5%V~2Lw)}Yu5f8?BeCQ zmy@+h^=r^v`}r1JqtG4xhzYQF4Q)-qIuDXKVOhDR&*0SFDEAatVA5$()X*{K1(Gxu z_01Pmd7K=t4N7$oAT?UbTDngl%3Tpe=se79e0AkHv2xj>KVCZUq_nvl78e!nE(IJ` z2xlu&MG(sJzh}gjmJ`$4TG;2BQZpbS`$vcg^caNiQ?S%SY|lC*vyu9fczmyh)m>tm z`R8?YZ%ZCx!C)5YTQKw;AfKYyVz6COh;czI09M{`*c3ILU!I=RQK3G&WkCpv62A!b zJy~e}uG)tqih|sLmi`>VMj4Smw(Y%lB|r6e)f+bW;I5*gl&4fkp`@uOnPi<660;il z=Nn5x+2;_827o|0L)(k;yTkBmH~-p|$e@N+wFqo3N*JwwK8*Vx_D;cOyO_DKq;jW= z!B@Kgefxf~UzNiN$DmA}h-+si7fC0D5S54zj=#&v{pEwGk&~MdR?<9cvk3U-C*`XA zroON%a>j(!gO%8n_z}7x)7rg}C3(0SRi5$IMS%jf!wHgIP5?qrd#9U1$A=XMeMz(6 zMX2I`B`v3>?382kjLt}T3_Ef zBBZ>Q8NsJ0`9@c!D6EFUPuT5t;@rw)#Ee}uBHdMt&>V}bv+h*O@qa!SQD^`sdKOQ* zjm6hDkV&<8Lsrepc@hXFW!i7xU*&2Paag!@c9Zxx5&COG*6Tq}u`U`VXA2#< zaf<($u78#Ju#xE}>0l!F(&sdxR0faFeEv@+O9jKTkDQ!H^aK60MWJoa;iVOzGO#QO z8;t8+gZwmD@J@f9B(rhq_C{9r633jaNY(RGRa0?@2x)uf1QCB|)PEmdK}f`l8cN8+ z-a9a}Z7H)CcC{9J#s}sm>v{Ols#sYMlVPwoL9fR2(nLrrH0dF4gE~5m)-yjovn(l@ zwXQ8}(99ljZEV=dLq>k6D0)5|_3?r5#Yeu=7M|1OCRob|0--YJw7&eC!M%|Vt5Wmjc zj}Kl?o_Q#-v8tLoX^O_>H%YsimbavGB(ytMS0SMgFn>Ma2CcA~;tL2?+(hQLhp6RW z1lS=%o9=G)^6vziCEr_`V;88H)6s1^vjMBvU#0UYm@aaSli0>N&P(| z%fC7#?1Nq*5&WB}-jb~S-~6cFChE~Ch2WWLJ|7qiwj&FuNltX*lFwBefU$_u$EWa5 zov0D`DY;#B%$OuFrEr?k8lml^ADvhJw{!CKgKfICmwXE(<3jrRP2DeJdhXiF1oI<%8r-f0=EqYs zLEcSwUp3Z|=L8+so!!@Nz$4YB*8D$AHMHTj^byjwsaBfE2Hn^~BV*JJ-ydxRfK-*S zY&suwW330{-ovApmR5vRS=N`Z#z*KH3ld$_tHL<#i(T>Cq|c!gFG>8XCj3rRQ@sRj z{H(6gkn`3vIH{#_emrFMomaLba;=!gRBjDX_yIPD$@EzN-=5%k{Fb;#-K8&wIB^yQ zoz3z&E{r~TTmjV_uzSHIlkB2DJ9W6;kXgcpM{JDsOU=H+HD5GlaBwm$t+2)-Vx;w~l|V+X4{s2JK1?T*`!V@#}4JUjlt; zLCKxl?qS=*gNgc7Dz`pCNDsf~#oh5L6}+S*P4ApOOw7VYpCNLKC<^l7GSr=ZH(E;} zJBmF?*MC_kdE?#iM9o`wgBmN=7QYSjjwAdaicq3=;7UZ{t&x@OZB1?Ugy`f&XKX)V z#o~#3szPv^#Ll^uJ+~!FLQHhBu8z*=gcEqajxq9+4@Ip3iP~VI#?8xHT}PAbOYHYG zle+1X3rdbgQ{iqfaaALE<5~AJZKQJrZ;OQjXtg1tq;EHx0Ny`g;CbB9x6Hl-#_P2z zCa0-&9mdV<%OwHZEmIHSrx0Z1H>u2CU~Dx^gA_O)@i}djNaa5D`9J7a&hw4kvW1>T z=*}4~BtwygN-k}35sEMiwP}k=_JWCg{}X5{7*V$kcj3Sri2S9PEf!i{QGp#KVt4%7 zWT@Mb^4I?bSx#@+^a4=LmOTc<#AQnl(`uTU|3VLN(c%&55FdbS&CSm0wt0OW%?SdP zd0bo^0tzt=Q`vj99Hl}BaYEatS#pBE+a0)C@Dd!Y>O)xi#4v}Z;|;aMUc2mEDxOr$^u1Vf7J+W zZqW|E5}p!!|>e33LOuJy2i}$D}QSC$)8Ec5!9?9`Pk!9 z%JQ_q(GVYlbz{y|2D|wu*9+2+3<|}5lUBGQ&GY;=l5dli7q`8=1d?}NkoT~es%-3G zOMV~WDG9vw>pDZT{DBQG8IHPqP3{U`swX~2JIeS(egZKKe^OHL0y9mvSZBE9CELB2V*lPZ5hX4) ziRkbGbFG^uJceo%uIb<)M9XTY-LgK|QI;CKnYf;nTdl7ZBjr|6(+&aBzv)fOBu5fu zhCKA~LoQvPO^D6@h?JigVtS8}I70>>!n3$ibLuE^B31RxT7pmbsLn?!!D0A8U6ZdY za*}Yc8m11X__S>fB0DkJ5aKe#l3o9ZsB$TW!jW>alWoq*l`LBZ^4msij|AZ5D3JQq zNw)dl$tX5zTJBla1WZbT@u9Q<%#1?z zWChc#_q76@EP0TCBd*0yQ%Je{g$pEdeetc{oIW`pIXasq@0bmHP}iu?3ML)bspcUk z;kCxXxc#INs-|#_Jx0onUVu?4g)+Ay(mP#Wr#CYl-wu?rKiw1j(=NA0r`ee-TABe* z+kU_JzO8~kuj2lVL%f7!lKi4e)5T7YbX^%Y7YcL2hd_Avppi?uE=7uaRCLP5D`4|sEJv*eX2K@LzYUKdAOfSlL^H5Rx)>lmFx zdHdiQ7Db_!Syd%}%mFTi9JhqWl10r=*wItw`V|X44k=tdN4%u3nNhXR+u5fS4$9Xn zn-bPVX;ko07pFNWgH!ui5*EWJ1MwvsQRu$Vd++Rqi3^V2Ps`NUl3s_Q>poy$8Vvr; zoj+z`C-g|qvAS2TRzte8+b!fgh#rsG$&+eyV0P(0{az)ksv6$ds*&uROvodz8C8g; zQiU)`e!BldT*wN#{2TxAyV@vu<4J@VP4bFmyl{!vLU-9 z338L+r;Sqzk|$_RUthG71rxH=wb;L}=%A zk}tQEA*;#XHbCG7IW5|G5~I2C9~I{-`tAL9lATY28=iru9wMO}CR8c2tXYM?6mOg| zMw_$uZS36P0FYKVKkXmCP!>(iA|*LVpJ`~a-t)lv%?rmibza1a`)+ug_(wxgCnf|6RXilM@q{4 z;{_qar)h>qD%X7Zjt7KvT%79Xg(wB;`KHJB$4Q;W@@dQmN_%lJ`Gb%T&eo+;@FbqE zJL`SwS_?^LM)O;WT45V?LbYfiswyfi4cj#Z1%Chm`QteP4-e1%*=gAe(N0KYYU=4C zpN(81v-55E?MK2?Iqma)DJ1ETbnk0b7t|swTE9w%OQKAL=MXBnRIg3AETFZ)5ArjD zseuBFd^x=aXF`a*!>O)LT^FD(_p#RyMU}r|KmtXwy1K;vi4Ry(IbWu>ew`vR&?iK4 zsHzzfQdd!Fe*C+9f%szsT_&#z>-re-o=M_wga0T2!rP&|u?67*wNhECR+TBNCq5aJ zO9mtAwoEbx0Rxc7Vv`nFXzuX`esv#P$k zr8@pv&V3x&c_(kmd0^%3o6mX;rZ>62s)DLv)Qs_7IqvtWr6)e*`|JZefqXlf zcE3r~j>y4z&Yr~?MG_{;QDxCuBd%(P0~zOFxSX$!ri6>ZRV8Y&ZZ0Z^bx9><1lvvO zxsL<+Pe~Oq?`ZI82#~Vz^lgWRT^TiG5u#u~*NmOk^4w{<{!H72%QW}t2;7r=xX4I&q-G!6K`@8+YCE_t zH>}=I5v~K1_mm}SJ25?I z?meDa%*@)%#8^xbI4xNKZ> zy(hzIkpoAT`$JB`au=gQ&zv;+pH8h@nPNqlX()pz&Z%@C8X8x!tY^zU+M&Ux4VHT` zDJ8i-2OV#X24N5W$Nux3VgWZo1iznFOF9~i6{%#-MNJcX3cSVH-l z#?NktaB}lKl8~C5U=|1L8t49j~2YulTTTwQ0C>=h0|g;{>7oTyGh;9=a8BjClTHHk>#n>7JA{UOLLd&u0oM+ur4UBi|;O*5D6BED&2J)qM4P0P*l1})T>^kkc%%Q=6 zMon7@`Jk9wq}cn8h~XK}ux0aj)=N@weV2knAd(RTf*5A7okicM_kh?kWTj};Qjfuv z$aFWlvYk$!U`?WEX2{03pi`5fH6<#=Q1T4I+FiLX28u1bBnj^=9?==A;2Qknq@0Ps z(z{PAF08P#;4J>JD_ak#LM@HHAgvFVc)|uSm%^Kx1c{sww%Fe6qE#eZ#fc(#bdJG$_SRelF}L*Bjyiw96Z4=CIFXJ;D+^3> zPztf9H3i|8mCMw0RoV-`bD^l~^4@(0JzVkfkNxTO-$e2v-D)YL$0dX9Tga5+vZI1# ziH;dL4RO%p@|Nmj{krh&)$s{;9*?Zig$qzs)u#X@_lAfdPEUcGo3^N^v9X~3Ib@n1 z8}iEUk_izQYHGoaD>N6TLo__v=r9A?pZHAax)n~a8gCfq8m&`u0;E@j)Hg&W^FxPm zg|_kQH^#4jJmrMJyNGGBAP^sjq{v5=+p*vCp}!2fbn3yKuCjZ=lx>f`q$8ezpcR8e z4B5mXRY=VDG`l2nMos^5g2Cakc9Mlkg_br|}okP!vyk}rt zf53?mE7Oye_GSo_1k7acPssBT=ht7mX zsj9@gG+)h+F`oEZ5mc|plkbE&AdSx|KPhn^_O zM7ru!Z3-KYko#NzUsI!j&qc0{;SpN7u{qK?T2`cdaBz#ta7%z)fDnZ&A6em_@m>n@ z*07l$I6W#wdH3W_7lD1qsJSxZ7lAI>Bo>CU?&349CyldP3XlKhZVPM{8nJ}D4^lAZ z$Mp3{HnX*f-l9wALVf}gKcr&D646f>nJPo2=ntkZbOD*NDyAt$A_sD`xiklRbGY-* zH=>?2qI)d;_y1b}KoNbrENQ^x$5UFSs3 z>}1%BFj-kj?2IN#G(^A;zlewGy6aqG;rlPJbGB9Z)jv$i_9RJAwo4V#C!f|Y>ko7}yRNU#W# zC2u7iZ?fN7$Lm(4??)^JG@qHAP&H52gS+V*<3^;CpZIjFZQs0#(wKL~i4O^VcvluB z4e{w%v4~?Jvx=PdfDBW2H!$|_KHMz*e`W)?)6YM|MIA$i#a1q@oxLCX@K(2TB zICCnztNWr=&)OU-lG>eO_{IeraV$fnXKxZsP%%-Zoz9H4PlgQR84G2r_78G>p^-im zh>zprcuo|1)SF@#DAu$ii*sfXjK8EPnIEnt>U5Ml;}&AGH%XhjG^o&^SL?%y@bhbEz-^0ZqNAnvGdSV>0Ai>R6qEe0T~kqSQ^!L0P98IV);RjN#)er( zMY>}98d{2pUbt$KBhOJb3*QB9t)&D)43sm0|O58}^SSxkYqY zyjS!1ju_M%;h3ClbDh(;gam4FN~(Il&v508VTDDff$O-szBC^AMowwR!Rk?>#>WNj zNjvo`PC3f5{!&vrP6~!w95ENs&mkFVxGhv>wg>iz{!nN>?^RTi&Prgh=o`p~z-u zyM_h_#?0BlUCo&!AKM--@~x5WvKhN{U!xM&16`MNLYcz$xLFJp7biOeePKan0*Du2c+HG`=lAt#tdy#fz0@Wc>qqbGq>_R0ie^u! zO^yHX^Pis9c;+KEcg$RwDx*zj)B2z#Yh?vpMU2)kW=GI7+bL8V@}N6eO?uW9X-Y3@ zm)uy9!ex+qG>ZaP>IzB}&#HiEdF3SJq^*SM=9cs-Vp1u?1vEZl%);^GTxRsgwsk*Y zi3X`yxHzxrSmmQu|8kZZ#ad6gdySuiy9tn9ay(yyWf6VtV4J(*7wFz=Re1Vv^7H4C z+1N|7eT3H~S>&=t+&N60^WOwqgR+=EGL%q^yz9@PG1pJkQuy>#flGBH^(j{PgI0qG zL(wxg6K~n6-sdOtyL(3s850GtB7po}rtLDb>cI8}-TbHCS8dx!jSUrao@2HOBw10) z3g*Re_EnXYf69;X$thyUYVc>~Cl%qkL`Tag%!&`8Fw37ttLm|I5H-S<%qBU7<618d znAAC%xf-ZiMTs#Q`whgB6OdN*bCao?A1;KjDVnBEwIF)5$zfAeJnaT&Zf{78nc6`~ zZ zP(Z0lRcX?d-cji_N{e)T5kaa*?*h^ZolujAl!%njLJLXg5Fmu!LfJR(clLMooFC`M z-gD0W=FDN35gaDT^E~&x?zOIUt?M#2EQ$^bkDU3AySsZii{;+? z!urf-MK8Hc?R0L({!qLc1G)cGk#5)Fe5eJoS#MC6N z#&vx^%PBwu$kRw4+ajkJ5k*F(%0u~Y&DjG=IK&CxnFF9-6b;+-InhexTAlsHP-6)TYJ@j)o#B$ z5jwW`E74-OIX(qTDAxNueM(}+OTt~Qi$6@KoQj0~}gO@TEljWqGl(K;D;d0zfxa zq!TlLN9ojlc2$<`_no$cFZd_$R_lI`7Oi-e`_*WcJxEtC=3Z?*^3D~N$K7m*R0&Uk zq;78WzxsDSeVJl(_}IdbLH|ViO5XE+tm|o^KK?joYLb1-mlP*>Py>pquM$b$Rgvebt}wYk6S=)>Bz(06`Vy?|!={<7dyb?7(I9*0b| zcj4OnjXT$Dd!B{mknD20`a0D>fL|RC{qk(FkyHPbo(5`uHlTRCeW@vIpgJh)T0>`^ zbDzbN^nmlC?8_^8&voM~q;&x59=(^XcfR}RXQc7Z$Yiaop193BU3307lupt?CUBJ0 z*BxDRet+=?Wj#_!_71%|!v+!kru2FvD)nZVzyT(|mA-F4r1<-)nLt~Ku%DrxUfo4$ zqwSNy6_UzDxWa|d%rveW&%ZO9?*}o9hmj>huZx@AV&+%vlPrFp>yg|3T%M3Y57~0)SbV=CYv4{!`L^T zCc9n_`|IY9%l|py8;FFsS%n%xh3VHbLtiP-*m>!N}X13xN z>mkVrbHBl&4h-){Nfu_y1wr#%?gCxI#U%>VuKn^yn3 zwAufM$>ZZd^&g++|6`x_L#X11iwn1G#2F0FoY~}Z2ugis`|9zHfdJ8Cg3E@<@w3@G zmwvzmu74xsI640RAIg8_mC1R}l#}{Kz4P+75u}eN&^RS)?Y{1a2b#NY#EwPQy+e*4 zxc7tN;^O%G=F(2{59sK8&L!QC>!=W}d?RQo&2<^Bdw0+6PtD<)+Kabkg3-F{8uwYi zbk0G!vx%bDDNdtf^2KW4u`<=uL}~lJ#Zt6C(8-9B-K{PA{sasu7 z=ga&lqfLRQ`|YZ1p03wb&K-`d9xV}I?{%$5F{?hRHatj)GkosPpFd+1yBPYM>B{-% zI!}LouCHHQC@Lu`SuAvfe#jTS7OGkrP4waYc;z`Y#!B^%I#KR4hV9aW{4yp~Mn}hD z>YQ|5r_ANwQR}I;vj%#4IO<5Q$6}I*jb|uD*TkgK5>vt`cUV?mpW9@)N28HoSfn3q zb9G~-xY{t1OG?~lIaMc5^C&$*|7!-7OfCQ#P74c80uuBbl0da_Z#%))w=uz=X4W^} z6d8d>xBx+r{Dg#|0a~u-A`#co-%yr69-}2oC@cTScNhGvZFgcXIv}8WbY_FxPYqaU zBKI4-T@9q8ulHLT9)#n8YFJ-iUtE#CpmAeuZd-wVdb&9*ic8{s&-nN88|B9LBHB3c zFkewxHaGxe+aMpV(TfmH)YhM7xLyYA6x+6;{q>vsSXnI<6{Pdh@Q&W zbWv9yyW$qasEwPRb2~ev^}c8S$g8S#EU}R#?1oARKs_jNUuTU2MijIAJwBK2<3CzO z`%Dns$Z~0V+O#fr)L2}#)ctNMnCRZW^w=%kf1zNR&>*pPKY+Gqk*9&AuJ5ix>%E4* z-wfpPa?)?`q?jLJ1Fq9*?L^mQlNM*ln>!YV!mq}E0=2(}wg@SYz1WOWu8h}^pY$P# ze~+YVL2|vz?XLqp1lxE>u2VEWL3UK5#Fwr@J29J|H z!jrsBvKTn*mZFY%F2dQngd}2JI7DplTX0r-X*D%1A%4MnJ5sugw_Q~-SAsx8z8?@h zQOdq4F21E|5eu-=!N!s7sP2Who<-y~i6*nTYroE8!d@j6lg!U8@XL7EBumQk!i5W_ zYlNCef;!JSV@JH%Mve+SyPW@iBX$;W@xn&sX#49AEtT*FDH4vF+vUCib zA&#c%-V3DxCN-XL3OeIkjKO}b(^wkJPuljFzd|j*HU$PRNX$;piaN45SWxy!6?Oq{CUWS2SF!46#cOh4Gk&k zu8y|2agBvVUv6&L%F526^37ZRM8=X`?{nI;9auw_%W$^QhMhb;}8+do(#pMs424pD)W zWUZ}*@>f2-``~+{DJk)=MAGbF;HleE#5 zVPW3QpCix{9(sCu6k_Gvd^j7)zu^av!Zr|%%DAQGEWf^n!|`1?M@w!f12?=wd)*aWzjBTqIL`PK%%HXKoK@%YA%i+JSu}zR_bt+LkwD120Mb{1BPe6Wd zMjSun3S&60R>ne4rsb^+pNB~rE-v+_CF&X)u5oZqJ!HQi89ZQRfbO*tKce9Rpz4~3 z%@%3BR-4{{2K&5~DPj!~4n026>x3F>)JXFr<|t&d4TQ8=Z#Dz=np0JG=!9 z`;K)Zba+L_@82P3pfAxT3Slc@Kw8Lk|KwjKeMLYo(6;DT$N)(si(@+GFyT1c3bUJiZiqkd6d?BKg)}>zPTyP^zWfBvNVB zQo>x=nyXu~grhmyWUl)idiM&x21*^uvpCrL^eLSmSE_!-MKsB^qS8c3@c^Ndmy=WN zG@=TD)UIBPftZ>BRo&8kT3i5f!_{!T?6g!%OUp3&r!5KP$CFL&mRfGi$67|o91{Ad zMMXtR0;Hpr-H}YDG#cT9{G>-x?$zyoJR*} z%g7---bCS7a`3@oPD0>cC+GavzDzyr=-3oO?es}O(K5a}T@Ka81-cAu($g~u=BZLp zmxJ>ltj`^8mL^a+<%mafO;FDCbP2`=av-B@nFW>^jHjkeP0H?%2O?=(OCyySqPc;= zcCT_JaFVU|q>O$E2@6vLw}j-s*rf%mD7LSxWC$I)t+jk%>`a`Pn7GLeyw|w=b|zRY z{H&`74i`|@#9i+4-`^WQ*p|Yyjx5XaT?rL;9+j0`j8^7Kv!;r>?QPii=LUWHJgn~_ zN#U@BW{AF>?43u3^+;hMy9$q{NRIU@sI-R)34n5LpZsIp#n zEJeDwC#Eku8IQ*LP`zHYh2J6hPR-5&3HVyiu2EJC|ANB8g)GbEQ6w!>*+%A(TiaBW zthr;s2+D6xzS2g|K;i++)YyBkirmkuk+{+u%;oLnWqg0|PiUx&&$hoX%tg~v)3W4< zjFvl?wn#549mJ^^kB(4LH2;~xIMz*5gs%}iXEuk`;sV2k`+@=>wZsBF0v$7*O`pPq zlmt0)db@~i=MLaL4mbcI(pUp?zs;=DL1g$4@x9E$@*|mTB9Jb>##BCF*qxi_OlcN{{*mp@TCbB+JC`c2Vx;VaX)(I-TA9k z3v^$ehd_$;!*T|-oyEfjUPk1G|Ht77GblXs90D-~U9DJlFpw_H#$TWms-}ArQeJ^qrKw zYYJx}rt}`#KnF1YBm_b~32o;AC-MLDcj~R>?gajhg`n{h4iKF#ytxD!Hy9JQ*%+LT z2t3VD%V6FSN=*YKxDKHi?mt-QBVD{R$b^`oBC$1&yZOM_=Qrge<>sV`Jl+ndIsW|3fb`Gs4%WDpSjiBHWE25We316PF=i z_`<-z7Qb3<6cx@PGD{?jbf-SHl-v3K@ud0t<+>W8AB+r&3K|Q|x zXc{xqK|C05-ZSWXEPL3EhlVX*r7X{y=L*SqNY11{(NezCABCE5uIm8L-1oF80uVS8Kwd-kj&{-r<4-=?zQ z(98K(vKZQDzt+X9VUM=U7XCTB)6H=RZdT_VK`B(Q+Q@&ItsGI|;cqvXtsKcQ70duh z7JhT-L#}3CKhK@5#Y@+U+;C#K0p3cpg&x;j*Vjtt?^E!I6zSFFlXQ&G(fXV}PLXG# zt_s_|covo3Z`0i=bhm8tC4bzJ_i7LdNxO1|#c8;<*Y3gRPp285FQILZWKGo1t!xKS zDH6@e!9ab-AMI49oq2%~`fl3{1};kqiJA>9v7H}pLVDj>8m*=ckac8yHX3dF6snE& zw6!HINByKcmu0;cbg7>{`Q)jKU%g-vK%-FksHLbCA=Do5aBv^)$s`@DG=GWVRr~>> zIOQ4})9N7}>gDC-OsW&PB_hHt4cb$ge%_nj)8&|cVd3l8wIR$=AJU}eSj}I?v$!n3 z_RxAvX1RneS4$#E-K54DD|G+Ng){S4I7Hq)$r9kPuO8hddKdXMAo4Wxd}$PIC|Bu^ z6DNK4Yw+m9^$d?iiAS8aFZzBS`q7q&4YsWdV(%V41hj5?#`7twE4R46rKN55p^pw) zvZL4{+M;H=%Qxj>_SAk{U}QX+KiIBtb45^wC*~IKaSJ*mprs`jhgYY3wkbmiizoL_ z?roCSx2{2@*Z1W-m&WW# zt&#mx8QmFBxyaJe(xz99eFlVknR9_r(%-&$D=9QeBT=T0jw10cVo8O|8M`mMZ9Y@V zEQE|7`Hz{QMR*Pt&da@Bd1a*lB%9=;IA!Wr^E4u9vYXSFi9}*4>TvH5&e2H#+{yzq zT5fZIbSOF16nxzW0UR!f3gy<#l9Cb- zP=JK?ZimGYg8K8Lg`n(%7YcnMB3I5Oc^w@Xjf~KzUxD<9t~!DJR-)#VcaNYrSC}Gc z|IF_B+bI~B!L3p}6{iZOvdMFEuw`F;Pfg955;KaQ3x`a7Ztg4^igw06=LXaCX6EKM zxid3wo|W;ju}M!s+eJ@BtEs60k-gH5JH>`|{4;acIA)kBjlX&ZMlho?w8?u33zwPMz}V~GOh7jAVsy{=4HND9EFV>QIz~1hd?EsF zQiMbAj~JTyy)H{FErmXR`_@>g?GE%y)#43q8UtsU;eGUxUWTg5J5vEcewOI|?g+(J zMea-UR10nGHJ@&eT~>cSXOkM1=##+=76bk(E190o_$qt+X!^Xu^ziECgvYwUo)kr^ zZ6cE1W%F3UXj@G~137Qa`RM(82_6b1~bE2~%H*Wl7S&*!P@^brZQD&5xAtk&mw;@B!l_OO0R}p@jdR( zs(DI90LkEIO(3~tS@=yPOfE0q9V|eO~_0#s&K&puW+6|^>?$G zLa3UWkb@APAPW+!|(Y>4wB!xMquv>?i;kfUnHEzG7aaw zUY2s2{(63I?XZx!!nvdG+Z>P{h2ts++BsY3wa#j8ubuqB-}l|S_P|OTwg|Lz&iW89 zXN{17KD)y08xO{{m3MFq9{S*Y)+Q}R2r|-#i3Tq4%S_dM_M+9n!E;pKiG()~bVHvX z+zbFo1h=-md+0q6ZCH{#&N6@>UDU)exF6{#f$HjmSEi; z0b|wQC5>6n6#n_=pX=A0<$w8O&$BWWRck8f^5=a>aGebgX~DFaxp(f=K6;n_blD(H z+D}!%sXubaz*Jk?RQnPW=knszlzZ0BBc{i0Je#3~R^`TVg>8of)=QVPb#$f@qX!&3 zoYcenTQejDxMk|iT;rM4()1K!RpTI#L+Rtf?w%x2Ug4Go-=dEmdG*^=w%_NLG%!o3 z9qw4N7Eb!}XT#aJW@~G!qBNpTJg4o z1Qgf>0Ri!C_2l{5gs7+*N)lIC>o?a~^+kO+K%T0owBcgq!~7wa^VhPKF4S0ff-^!x z{`3AJt4l+>+e=SL*z*3D%e<3o~4P%Z1Yh zYP2_1QD0wA?$13fW!ne|2*87sWwFYba-fqpDiIW111gNz5hqPz6*;s}w?t@reG}$; zZc!L07MhuwN@6g06CR=r%Bb=MMpn+5Xlkr4?mMhOsCR$oRHM~zP$I+{9cIoB`ToVS zhvUPds@?OI0lv$NH5MEDetv54g2*G3k6fCF6u4tWt$j6GTm?l^Zd*>^GMD^Ndp0V0 ziHRtrPe7gB;TDHf#A%%uZ(m@S3FZNda1Eeq_&b2OLk02&9@JPqq|YXBruhCW)2>&# z*r&#&hTxGWVDGz&lMp(bs;e;%a9$sD784UQo19YXP<3;4B@hVyM~QlOm zZNN^`>Sf;KpiiFEL681;fxwt={&VN$d^etZcy#SFEN`r@3-l)p8Ntt^cP4)=0-Fch zqr>eM{{vxcTG$yz08waxvHqe`^t&XHy}jK|@A)R6f=IjK1&*`*_se4-m$6tO=b6+% zJB048tZzm{g%OZ25}u0U<<1Z?9!B~My}b3g#(~XaW&E`e7xn4M({|OP0qCvzDW~y> z@}r+0%WDXGqxVX$OZhhrzIp6E-(!s;Z#HoGU;R3KRBdorExVgHIXPLY`H(}ZiiEPX z%^)I|{M32+&kG^<7dnKHyL*lep47)No`VDeQlm)UWqEm9J&lJu$J*Mqzpvhdveu~+ z(9zyiZ43-%%uIetzlZcxF~xw|!>X6nB2hipL@B&>Gu{Lc!#tPA#OC_FN*i~wEV+ts zdjZ%Z%j2vZfZa~>FTj){uoUZ`?LxdZ;MUeP^*!pO!;m~|-vCAT2sRxvv$TX1TC0J@ zNO09bl~;eqk!R8ae~_P_0LN{LI5d5ooR{IdF~PNV={`%x$*wnKtxAUhb-l50>`m-LpbanNCqv!G>G=5mBaiy<=QEul6 zN!Cw#jKF-ZHB&wqM3kNuKJM%W?NeiCHiGJ|Ot8CcuN}%quCJ|Qhgk$gZq|leK-)jJ z``tQ~?Az$)>({7w+iQJry352%2LjOu&NS6xDk-Oz^Uvw=4*AX4VIVsEx-&l-acUXV z$C|~)4Jdz0?KkKjE&f;NK()=3`}VjVh8&-O;Op%(pi=NZ`)gM2-;h^RKzQN(t2Z=H zcs0B8I~8&y!H4m>q_3hf2q*8qqmRb0tcSCXz%c=PbkHu8a9_rMZGdaD{xiLbLw_O8 zhYNdP*@3HeGd+?;BBffomN<@wk#$4E?%gj>5*(21RzFhr-TkN*B9ucp$ zXBHE+fA#9s_a?jHfRz_(v(B5afLEi)u9Tqe1E;66yZ14k7F<-k3)@JeQNB{PQMm)9 z#E<4o0fTSyba#S+#cBqPDl6U4=6lF?D}?hxOwX-MWf(Ac%KA2$Ar7U{pNd+%;)a3O zNP|wIevzI62w}=&(xSqRittuf$<0nMp`^|e1GjxvQ<5gBNOx+-q&U9Q&B#&Wc0z3Y zNNJftlnc{@!2ZGh){<126ONs>8qCOXotbU5Db;IJi({~O1k@`)RFYuo0oLoGd~gkQ z)UqMGV}2-aNF(sH`9Q5TEz;wf#Of3yg!LxRi4P$mnnh`z*xS{{b@m+$+3myJ_wV!6 zAH=o&xTYNJ=?vu%do!1gw6|yT^Why6nJ9tDraEe!O32bLSWb~{!r?sdC4|#~H}5{^ zX(kX5?L$Mhzsz8%8rbK=Mln&*_fOC$eMdqr9M1uxUgDZtTE&w3i@HeY2S*#+v{JVDIpKQTOM45D9~!>T3b@`{uwgSCEgd zy7V;NDNskN$;5Qeu|XC9HuNFn`4I{U))eW$PSZZgTp{whRA#>eU&oye9Pdrg-H3A> z412Dx*&P!lJ0>RUiSH&Pkcv#^-pg5Jz~s_R_STVC8q5g|xPbxRYAbsUXG(jlu7N=f z@vIEOiDZwK&+4tcz?oRK@=`_KKY0U;X?Qb)is98EzlkU5 zC@Fh98_#885H<%&OpoKf0f>Ac&mf9)IcBuRp5yAcoDXqsuF_4R1#n^gFvC4x$H93L zhTH3=tL)hDHo*cgfl(uay}$f`ctEFUg*^;x*ygwxEiRDF9#DS^qjmkU|1X3ukn3ix zu6OfYDsB6bHTf-T@)FI#>U|=rky}P21}g#?PzIZwDZ0BnO*7}msPMOly23vwX}c$H zhH5pz0imdrvzxvY69z?G$~0Iq+{fjjriA z-lPBAW*T0kFXgfX2bp_uzG$&2Qg&v^$ixRMNuSMmmP=Qrv)=LD&0vRlGnLV@$N`7V z@+n?}VsjHm`|+8p8cJ}bso_i42<1@fr=O637*I=gCgB#vb@lY5Jb?yX+8aquN!Qd2 zKZqS@y+B$z`kR5cpcHRu6FiWPXKOL7>~()UKoz`yHrgn6|SsCFo8_`M2?JlKiBx~;7#w199jMyXkrd22T_ zx(T{;i~0ft!WiXYdw+j6*w&5`Jhb;d^cw7mK$YEjPLM_(8*{f*j`|}FLq$Jq$6~b# zbXiyvM$L2U3JYtiM6(nVLqMnSz6Ek?&_pTsM|!)^{e>Z^CK8Eykiivpj^UhSO8P^f ze5~2a0UsQ_h0!cAtR%7w52(gjrk4kukh8EbQD58Mr1VyI&?@Ctx7R%yzX9@s1abF? z0uRa0c6PV8%LEn@(ra~vDZoOGwR+Uh8ra>pgBnESYB(OKK%pF?Lhb_N1Z}c=y>Xak zo`;*87`u>!OAiK5Tb63eO_gczCbZ@MUIFLomWD%Ip98$oHXZcVnO4`=3j)4={rW2C zS#*L$Pog&64y_dyy8C*UAC#yQ@^l7z2BqdTO(?zWckf;{e5yIKNX%LPD6hOkk*W8n zv)(B~(lNL^Cl=!zq&yaYfvJ(6-qL$uzMCaICKGx64$p}v%^+#GfxtYw(01Bo^KRUq zPn?9UB{>j@G?^a9#ILp#&z_a3_oE#MGm!y?th&I4zr144#-=5+n(v1JV2Zc*`^NPN z45C>3h3>|%yokvsF}aMmgQB4rpcR+$^NEG|Vb4ZE)6FIj#JDFP6+QJoMv97vyAJK0*DTn$hsNY%)OXPw1DlYinY)`=cVcwyge!3;u0Em7Vn%m zaVQ^idSF1e)U@HLf~snVam}9ffDw3arrty8WuWEz6hI98{fk1OD%@{;Ub2K$7Z(0m z&5ZzKfFa?v_P-}?VPYhp_9O(QOP($venBf$*37`bAXkWy;llomdu9`&-%OTgbAB+V zK6f^S>Yop<{*fwk?b7wJTn{yT?l*0uF&wNQ0YL%gXq!yUA?keYkB=bd$7rRys26~G z2xY!!`SM+P2$1#NP?@ij^{PD31Z^>O2Kuus(MTGjpapVpFkia>ps~{Lr{@7&gEx*$ zf0X&+uAro=Z=Pn5Vk?4?5%OIYfWUI}n1Dv;kNl7ol9zTsfLxk>O1$Um_W*A)GxoN5 zZ|H`1c>ZF%O~b^gqnf3oZrx~$H`!Q8$!f+5Po2Ax5Ua1t{!IQE`{kJKG;|L}T(+yf zs|&F}o=A@!mg5)x)zL4X33@Dl_A{WW04D}cUJq|Ui2*Bb!QrQPs~DvCOH5f+RZCeF zVADwh=m(woh(|{+uC6dQSHFc^vtnH|C@XC_3a(D_-NHcc6fcDW=8-aWN8czdjEDk> zcd!8UOSKTj@Zcy;%HSPYzVf4VKU!em$p()vm!sM6w_L_Ak~sk@t-BNNMDDGVF>=bT9>!@t7(GOinmQ||=XNAqwFl2wkC z9vIC--4h?wKmumBpqZ{o?kR!q=O$Xsz0b&$4Q#r>z3f7-E|%MGr_f?2jyF-asnpM7 zW{Oannw)&&o=EJn6CSYyx~nE{UyRG8@22<_8Rx96ZP<0<^bht8W2;Lv6=M10OcT{8 zf)U0@7faW7@4mDe17o4PacjSZNX#tE0jpiG18-h+_JBO?0_nJKK+A=B3sZDmCkFIP zrN*H)g@r6kX-W-lSYn#bR%nQR(L<^D-&U$JZU~cvoj~POf1}dElI}n4<(Tl|)axM5 zVemyis_x6I`+}tTi=WS(J6rIQ67!mgyy#|VMVG3C@BCNrN}#feP|DUXQdFR1=@;qJ z%jQ_x0yyls(yW({n!cwC*uYL5Ca@*2*z0JKP#*HE}!FHgIvauRjmZ{#w?qs7XEQ4>z?M|(^Z-Npgn=Qck&r-*km2F2(_Ok;u2E)YPY@rhxkD)63=9n1Kio4B64C-0@UN`N78J5$!=$PR8- z;X7iRa2W8*p;xj5dm^eN&3Opg&t1Gru~dR<5v?*ZOCFB7M{-H0h3JaM27nF-kSPRy zgIl-#JeO+h9suVYGxW-+6v}8#Pr<5vrlwD^ZTf+flEPA>C6%PTLC<>mg}Etn-@gG#R-Z>L zxkk@J4(Eg@Dr(pzVzpZmYV4h69jRH7!p(g;rWdh5om-^N0V^T5mo=lK$-tt*gUU|& zEoH!+rQgtkaW8&wRWmQ$Uj1lcA$ch^)zjGopIf=U@fJ$F@9wu|gV#^fmjcSoz;vR- zJY#<)yx>+aeRWiV8djPW&o}CSD__yD5@Z7v8TWX&c`8R@ZCRp;X9;eu>rT{Gkh53h zrP*{nQyvqw8YvVbC!vMCYU&F4C4}Voxqpc}2ZmJH5gW^T_^yIr3BK(_1^kxMlJw4ciU~boOja(J=`Y_74!w%yAZm?`1)z_Wiq#dzIgISx=95Yt8U4m5fiE z=W_aFZ)Kq~s6QH(hhZya@HY{2WHQs$t1k=?O?1<7geg-tLTA08G&3v{t7t>xC+5c|uSY+p-PLRtTv9fq`(8NGG zZ-h+cX0>GYH0z6gH>sLYS zz~ww`1*;*{NkW4sidKT(UY5Feah!xSN#JPk#0U7Hrv`hI=CCIB<3W3++8(#I-fA&2 ziUlA7*72HqLWaeQ`3E}`Z3Jb^0|UqgX-OETi@9m|>-z_r0 zy;W3Ha$oA{UUirMc8MM=Z>tNA#73Wloi04$TDTI^%OW!#l1xcqE)OV4=NTx@X`tZ4 z^ETo1hJ=N+6$S(aQ7BX|$LP@D;3z31IXSt(g(I!eD9-#)4ITAn-qnTP(Wtb2&kly9 zZ|(f%3^;SgDh?88x}f}jV4%0HG0(($%~Cu`E90?_`D%wiL+xLWnx1}?pw~36 zlzl8C(r+jH(hCAfW(S0ukPvB6zrxa*ZO~PBTVGSWEqeR?!jH2#-Up4R!VXu;u8dQK zxB)VmUs#yC27!n^1^DMh3$BcwudkSrB<=qZ;8Rrm1bC~NCG`a{yoaBE9HUy1CeU3_ z<3c$l^Ns96P>qv$0(g+3@^Hh5;ymKH*&))~12NncD;-d9Af2T@%PIb-K+(`G43``q zU+zFS?dSEF3fNCa_xLlu+vuK<_=n%}j$&<_s4*d9M!U<#Ey}SlHZYJ028)o$XPljo`nlvpk}%R@W%Tr z_cFL4wUIboy_cUHoz`~n%nlJDsj4dWW%8hP?|i<~oel&9ItC7-dZ(a}c~cJ^UX3%r z+N$2E71XLN;t=SXLYk&e4rVN|aaY~gTQTQ#{~(1q?(_T>1*F5G?BS=GdKWu7HueDH z9CcO(?lFjE@q4%C{wLa!m&JcIA}R`&Q2U5@1*`!Bu;ku0ftLkI95Yi|(K4bpTfvH4 z!Awp{;=aUwX{^CE>YmK2w%z+QS9_zRggo0o0;n{9q^p1gu^i5Fg?W~hMd?%UY9s`* zf(D58hx5`9y`de~F3+4Y|lvIpPc6KaR8P&uh^joWskaYyQ^Sy7 zUWgQkV?J$=c8_}I_-P$4ja@GXsk96fiE>6zn zNCD3DWgd~sK_?+Aa`FIL>m^sQTK~xZ9pV@RKqnB-#o@*d)$DkOx38YOef4U#?mm(A z_;M33UWX_sDBN6gQ@X^2|3p7@cRWYmA)YBLsn}ycp1{Dtgm=zS;>{V>>^&DWIlnpu*kJ|* zx}dLTKG38%lhteIZtQGmxGsBEMi^^L?&v6x1207%xC~l#Ei9of4c0#d__|DLhgnj= zIvxF{ZK*_R^$3*($Dna8YcDGcn5J?ctAwI0gWE4JR|w6`&ID{yw2$x8XdPFGm9l-|LUuf2jE` zIoE_=0Xth7+7!gLB6tg-Lzkny3qY;3YuPnUq>)!g-@aWc!m(e!@_ZEtQaT(%XOd}K zS>ach+1@F#@d-)Nv0C1WP1;Jqhe1Kkwz6kiwQzqou z^xQF`TR>q?0-Ibs*V4lShe-1Q`mmIU-BUrx%k`MnmSVNT_QAd^FSm#=r7OS_h`^ia zqoL8&)T~?}bNu*@5nQK|y;t3Nva&=nTM{B3B zw*I2dZeZcs_I5k~fz>psM_#PnJ1})$V33df2#=NR+8H;M8LYVOI59GMtAnrA1CXG> zntBlQi-4ypF+3Ls+Y-S zNfuyo0+M_`E=)mHs%)9IGzeWb~kL%&ZsDly(+)V2mCw`F2*OBy@DZ zhki>?!~O;gXsD4G^g-ICR=+EF%qr`nc;*SY-jB^}L_ zzO)-!?&ADz?VIv$+99T2_($YKGRpa1JHEi$L-OVxV4m#H63j#e#c)FbzwIZ(#Rh3; z>8BiTodNXDAAG@onViS<-fA#VB)7)7 ze|8ayPLrbccdxEia(b63hOq!zA{RDY#By{1oC3Z(t4{dL$v2jH1Rt&7ElQ!I1IYAu zm+80w#BhQA(Rk2Y`sHYkdj=uTfe&CBjC5ej*KKiHscNNHP)h$%fsftSJRbP){_?Dw z?YRg99Ollz#zf(oFL^j<*dG=_b&`b^BYfSU#UYKN71eNTOOWzUrU}tFu#IHz22L9XAtfzj|co;i>GM6mr33PC{Dp!7qcnD=POMV)1c) z+%?(?r>j;E(3t{?RwEnT+}{k$W$Y(jGM!x;P+!C~I-VCwGxicDt@I*_D;1Ue|9;DN z%sF^_0KBe=MDs0NXsButPV&aX)eNj$gEwU~$_D!-Jft>Y!LRA#Q|!Td#+D0Kdb0-n zw~C5}UV&~?Q&Z^?Ee_y8UFozhU|ege(x$GiZo~&np`mt19UHcn{SI3bJd6n&1>4B| zU*{_i*IKsHfHv)x6d+#B|L~!T7$ga>EM^qBygYQdm37j>JmIC09_=lf<*2c4CRP#S z2=@pa-6QmiZ1ks%1{o3QrNtFv?AJC%=f!ncI7*8P zXYx+cbAy2W0Zfo4CNh%(pcZMaq=BmN@B*D&;K6Yechtzk4Q>Oxq+quryV4lploTIt zv$(bUD+q9rY6RrAR+{5ZHv>pg4nN=UD=P~|^6mHk3Ft~%DjI%!BXdqgY;5EVEbWJz z?^u(3Fn|`h0cQ6~IOz~j0bcR}CXT*MF>~}|sS4SYi~X|02Zk=z_*dHle@(%H&pQ!3 zZRefO2QiCrYugle36&zarmN}Mwq}rXe-bn!7O&4%^Xdfb>WIF-t9*WYb~s@>O^9)O zh~Akh2eU=g4Hin^LZ3git}d8;CWw@g9oB$*&!_0*w6QZiPSkrC@Mgog{Nwqp*|q+U zrvXz54B$~aM=HUHRk@!SuMoDQLqu43GKGq&lyKSS+ZBrH4vqLyj`?vwV;rtQKuIoG zB65p2mVr~!)ZE;~#1jUi_Mi^%RHQ9670hISR$Ym61EBsK6)eW*lvbR&%G7?ZlqDL% z$jYQ5@E|hsY${R8N!fd(z*io==_+!k<&$?fmHpBTbpfsu7NP{dus*oFTdlKSctfje znltlZCgu`TC(1_XjpRwKEpE>`=k3$G>X3;r_L z&M+BuQoHA4X{l6DfEYC1UUsI4I?R-&Pp+Bj{xN{sTjnD=@kwbW=4N`1eq|P69ZU0+ z!pJq4+2ia=q+*V)RqX!mGIb*PID7bT?WLDuPza!8kC*^XBhH~3jzoR?_QryX3&UbT zI$y-h9LK#zo?8dm*z9oApg+>sv+C_g?=vm~kE219h<7l#t2%63?7;`PL9CXS9tAy= zhu1gLRfSQwR%o`1Pik0X>wQ?cI{1q!s%s14+LF~|_w-y!H25BLOIx)+8n zSqOnj+eHDeui4m=z|>7y4Pa;7=2lLFvW=ukdekd#FAFB0pK1a@7TMUi6fBwcjhz)w z9;;iAJQ{J)8$k)GWbeB9BA4R$MR0;r#_tP%UR^G_OrzWgPEnL(%_-kY5be9yXVP0M znc$ai5oTf;xWo4|=QwMJjC~h(Chq@I6Vgb2 z^w9q22Qb#l6)%(_0wd-~0$%(PCF#g#x{1!*i=301nhKMBe^egK_3)~Y^qA{PPi8RJ zQ3o=H_khxluFcm2#!Fy41N*0)DY|&6n9&wn=iE830*ey5dw=h;AGFUp^*8)F8^?oU z`45`#PMkqN`!xcabCNaZuG|AM`uGdM1xHd~gINvPk8Q2A2yyyDT~SzABMfhXv5OXzYJBudJ}+SEdZk z!GlBg%X}3BIoiV6?M=pAK%(*_yR$Z6v^N{0y157Dy>xgq|NQwwtb}q|pD0l~K5#!M zt*)*3f&qUpUj-PVsFO4xhy@@Xf&cRlybj>9sdb@f(@r1h(NIk3Ay^(_3>pNaQ;_a%P>}AH9J;$>fd7H(exLh!pS-sx-tGTkjeNjO9ImppWsYphsmNhNssbR9FhGeP%pG7gbYRZZn@KoX2}82ykR+ zlO#EBj z7sPXAdw*x)i}a04L#Zxdvp|9tUpo#qn}A!%HH$AL?*yA8^7;95YCJs3@7Mq->i;Jv z!ZqXwkt84r5sC{PGKINe}rl9c#T|1EB%*;G@>lP&KS#YOH@?x|B{u*jUpabg6#G)D97 zB!UL0A(>OJBlaDwU{Y&j7*w>2lPfDb--cF^$aky4uf`z69C|$`yIby5q@m5oG%AEDaUev|A`gv3@pe57BbaUP%HV zHoqZfw&UB|!`(&nUV}gEi^?%k7H`11FRK!lo0et)I1?2{d}nLT2<&QTim(=b&oGX* zeHrm$WYD1oUilg93D`1>yUFJT6iD=4w)~PfZp`)b&OjTDNde^f_99_h4vf=RZ<=7KHA^W90 zD>bh=kxgv>i4FEsBX|(B0JyNb)L+z2A|qIkrAeM=WoPFyUSe3F+NyPYP~>vKb)JNr z+(>P1G-DA?Y?8j7z+MCW`M0u2q5Y*i!cj-Vo(3W{SQHeXi(Ep02*Em@ytNErEyx%Z z-mZj0_NL^W$lJ$9>OI+z@i;m_nPFYK`!WDPAkz?a@FE4YB6@#~-XL#abZ=N#>+YSq z`B|w7sVC(dS!FB5lVz)z>;kRhiSB=fATIV*Q@2bZ**bf0xb$;&^Ekwc1H8{y~Gn1sOhi` zUKjAjFz?Hr;f3=Zz9=))a-NoRi9^sQN=N^WDA@;%3njIlgt)S;jPcyq4P8sB?m|1$ zf`brYD#)!i!-pXOzUqDHcwkQk^6RYgwPJ*JdSM^MqrqQSjOH+s*QU-0H!Y+HgAjC` z$G&{|_UzdyzuC&s+2yFjK{C z7r|C#Y1bFd6<>Z@rbm-BjjGZuuZwLva$j@bz~y^ zfsJ|`VFJUEhz=1K5UK#$Dn5|>=)yKzS53_%WOM-xsg+~W3;N+E=B5l%(;;s2M45=x z&SK7uiC^&cv|w3QJQ~`nxH-dC4#%sF+nbmOxM0|7UB3J$6-dQ&88I=ti0<@t6_REV zMs4z0^2(IMJ%oPqb*vuBWc@F?W8iMn0t72lx$|77kqTGu? z%vD-S8WN1t7Rf^S0~89X+~cvy0WFtJp9=Qhzn*xq{ycrrQfFS#u|u+J7a~AN)?vojX6E>?I-YDIu;ZT40Mpp@w-_+vYn{dh!fKZgSV9#3hbNaO@!yM=r8E?*X<>rppSS<7n`$4aOi<>t3l4Ln>ux*oYMLE;ftY zymKd+J!;c>6ZzfhjCb3*)syp=t(P{_$h1x|ys^3{BD~kaSD`FYSB9NXAY5NmX(Sy6 zt*Wc$+Rh*CjvUTM4m3t^tFZ(Fhy7GzBrMNRt;j@wHSc9-oY2LJmCD6Td-FkRL3@Ea zcV0c0>uS>|L+^@b?=muXR~>$H!=NYFV%owggm?$^IJTvqfw|;|U>J zF8c{QS{U>G6+y$Uw$ie)BIDks){&#=ly2lH1Pt{DHJA1GKV)Xj2TG-oFiC=3w_K+; zHLA>BOV%8i`&Wx|oiZoeS!yUVp0E~2$C5=#r@|)C3^fKaNR=CIeCf+ji;|KcG#xd2 zcybI^41_LUM`xAUD)ZBq%v4nY`i^#7u;eW%0T>1x!|_8e5aPYs{s*wQx8L?Ubp6^w z?J-qCE;@1X_uHe;)4lz07uG`$qFFifZq}U`+ue7v^rGZGb#TBi+qUsrR?ah4egBfn zapZl0juO2X`o`nz2dr(NT*@6*RzaJc7Q@Y1epho-UFru8uNGRu0#_=_VC@fXTPexu z0;rYYe(c>Gl*4i>k4z*VugO#clxxrL++?A*`4(3TzqGuI*nG6qe5G8*vT{GM%%W8GB!f@)EJjLrndqTZ1a6#x?*WSSG;ZR)0 ze9aR`|I?;h|7!$CDYN2NpNCqZ5=5EoD7IDMsRwMrC*J1c93+2uHLOK^c=thFfUmiJ zsJ_2m4GCNNQlsTTJm>H_Y{_9EJO1x1WM40SPc2wnvbYBvJl`1#ELAF z>}3sSrvQFnt%~ialmI~?=x5MQ4%dRNo#Z=49Laa?F_$QmSkUqn%RKgjS`?~b=s)2- zr-LOU5^1ZtoR7Yp(%U4ZW=}3vN2;yO-npE`d3)pmsGre`$wVM8lagqj8{!|2zKE*W z99Iu)wB4@=cHWJDoRXb&g|?%r>t*>JdOHul&F$^|Bjp$B>KoGm2|J2G;=F)o^yC?z z)Wq4XTq7B&94W%Gfv1L#L0*Q{_v{-{eD>-w!ZFYPE z0HduzfQ)2hJTpgENMlW;uEeJ5L>%;$6MS}4x&(M3kACj(AG@HzbF}iXGqQ`L!kwDe z{&7;TT(CrNn#|*wl1=9RbTa?+$Vdh{7ZC9+6-Je@RrPyxCoMNySXcn#&TqeJw-o!& zA+Dn~X3j84U(^&|p(zNi0T??LzN`8$79(rrp-ZH$VW%;n!-LCr>>qVOShF%OdWg|3 zP!GXAz96ED=d;*CSU%Sxk7onu5O|pTrZyL8|QBJ1GA4GdtO2YOgO98cF~bW~R%qZj<3f_w|i_))>+hEPem$xe_Dw zOP-!Q=A%v=w|Xm;4-3r$h!4;09rpJ17|hPFs1*#ZPa{!g!kosjWo1M2pYCJXv=r6U zbg^HQ%4|I?ER#70Wo#XRzuYuv?PIXa%>dQ_1jA|~BIZLSAPC*`r|1S8uEegMl}Pr^ zweHm@Gj8S9lZiC1E5uaYIZ#4`ZvLqL<>(+W$#80FMx&?1JVJ?wkah}6EI5NL1Ge=* z&oCK3JlP91QbrZw%5m3C`wjJHd#YwY%eFLJ4ip>OOD=`FBTGg-i5!S8STP)dCC)%) z9y?o=Vp{Tzv&4Y;wMDfbXxB2K{n>186>7gLoDPY>h$7A2uHcT2psLpIu zuXsd_IXfd%D0PcisIF;dFA0fLxaz{z*47*dGzP(EE}{sDG79OaNSpi{Sq01DGXD^0 zttKfjfybLUoBXNi#LG<)*@Fug&bvPa$=&g>D_nG0NS4cCH*8Df_wC;N z6P-GU;nwQu{u6j24ZeoiZ|#bVEC-IlS8cW2Mbghgqj2fRc5S}|bRHQ|*y+@64clLp zqcSxj`ow%`ZOqCi`_vA$r2*0Hh-0f7$aOJP(9@&WI$rzygxeCEqc_h%N4IK)@-}Y? zx_ht?0d&^OlW#R3Z?-Jo{6@9vyf&H21(NSnFIMRVVLP%O0=TqrfJtxYpj zptf=32ZC2J|GfDyKVjU%-AoO5e#af&h0a|OPXGZD$xiUh=NnOR{Hz*|b0zq&B(|?;y?EF>*fG20^A9R z$5t)5BSgMH`6{VJOHFRHI((P&1u|bXqtmFgB}hF{9TCt`p$3j1{*w%!#E+w{{nR0k z$xOy!$Sl481r9YO_YdGu^^dOq3mhsbKVK3lFfweG7EQYX&s{u%zFa^MZXxlMmUgym zb0%23t7hnmj-Cb+!fcV6YEpkTSXto*hM$cE>|*airPN-z*JOFew^2b+KQlEY<+|*H zLN>L6opFlpl`6w75BVftT45DT{oaqzg@sg-But}q2H;sAie8kO=^wTI4P07H1Kc|M zWE9K6RIQUkEo`Md72Rthh!SGv%(-H~Z;fc6JUTGKV*v-R3nrX$A{|u-5{Ys823d{bZVa z<9n8DDSh03%-%Y6uTAeY7D0mexVV^{=7SG~)NQmRycPbHOSt;`m^@3xGzHCQTAz}^ zQ~)z``+7axhB<7Jtyf$%R?U0vP8#AMIrJRU3PQagHX zS&mX(C(2CQ{rYT@=7H_h#*KZHvi_9v4s}nN|s`{D4{;*bFSTM<$ z3AwnQuj=D6V8oFaEu~gr5kJ}KUB#`xqyOvPAs6B0{i#inosu?Ir{8A(6HaNU<|+4a zWo4mQkXal;6_l}3?Jd-Hn;pA`7&(pQEj3sRB>R_B0o6_9cjF}uEgs0~5+qYS^T6qJJ z(T=P|L|A91sUIG+8vKU`4nuB@#s%Tv8VYx!4Je_5{TXDE+}*)G<~oJ{j?ME%wmSru z_oq|A4h9tVh12<4Eh`%QA@j{Nt6?}=RueKJ_E1)w>e8Q7lE$j5h`D*|czk^+%Exjl ziush_n9$`@A}2_aZ|6+J*x5;ee^^;zhI#h@WJ&T^l9meXSNGrpBnuD0;T39NQISD|7B5c%Wu~B#5=Wo%faW_)tc5PFafli+mxAwB zK(>fDI=&a3>?*Lq<&a3zlT*RZd@}7F+}^=hJZHKNt*}A#*w~x5&b;J!oUg&rQ|7B# zCZ8kIsVX@QO8VcGSCH@<01PJ+iI7b`1( zQ%X$N)S{B^TCRz+%4a&eeRC*eqy`_(I&Xqsf7(35LJ_u&0gNLL;C%T|M=L{AUR|!# zrT;yf#7MERghrI@>R|0iYe90c-Z~F&t3JYOOVuxcA7DJW;}0MS*_|d4O0^hm)TsmjDV`FN{P$titjRJ|8j9` z_MJpsxAB_ui}`~bF8kn)>H9!(X~&JzeRk>2jG~GE*)RRJG z$nNG!6^A&k;MxQa>%7Tbg_wr`(FL?Y!>+WZK67mF-{p9)&bu|QU0n{hq}R6Ak~tN? z=?{fF)3^6S8oj@80uu3G^D)Elz6%XN_01l`++NV*#Ty7=&aEg{1cyP-)1fn35vM(p z|EaC(f8eeD!gl_`cK*V4{=#hPMUqzTAt`={&v$zhw)N_=H0O{;X9(s-%BlY;YP=|a zXSm|XV(PUXc%^{wxk*I#4s)d*16fq!iR76suGlT(MED{wQ><7wPqi93?vVY7*US$K z^q3SR6!z&QXZI*zv+aN#QXdh7cW4{gl-vE(L*oV_#ajt7_w9yR3fw|uJr_5R5Q!cD=nbk6uAN9--S&{L&lyCv(NbdY)R$mM8%Gc%Fmmc1tyw=~c_ zI44&d##7P@@4+cR@LwXn{=#p6zeXkdGApLU{f{c>Y3o{z7>ELU{f{ zc>Y3o{z7>ELU_Ow;(rU_`9FQoOj57f$Kab}bQQ=HE7&wumWT?ff`2s%?Uj=t`kCRB z2H8xx8#4NMcs;rWFH+N5dOkCrGU@Q6p#~iVK8au721T1m!3z_XUIPv}`BNxJ zu~#unN&%{IT**&L?k|Mrf171GzDL!Uer(pZ)VvER1j7)|c66}y84w;$6}Yrt>2zy% zCCD(x0|f$;=DMOFTzp_Oh6nu4p6;pk@t?6($*`DmhxEkeIobFRSWeEHoC=e1tlf(; zQPLW?UggtNBY%kg;IMBAMJKgCu$l3833+t(!fE)KWyRr27oDrEz$eJoe0$$uYpHVt z5+xz<@Q+%rqSYTNC*I&5K#MwDh6Ki-pbsoVg|2|l0@vzG15ugW>nrOJC%d`5p211( zOU=uNS&!@K(PDjB6W%)us?#lxD(M(|tyRqmaG#Gps>cWLyJ`;kpG3y}`>AR4lH|e` zGe^6^o5P!21qG0^@S{{wBCo_`c4cH0v*oX;#fqTew~stJMk6Mcmn#5(GS4cBaV>n= zi8!nTOPdudRDri=Q(Idl>VV+=W2@!{{|XXfunFGS+`bK?uYi+>(8+mmo`9msjRJ4&@TV?!YE?$)RBSAtBp;lmuA-(!b&Z^yj*P57C6MJd zS9F`=F65Dgwa!d4D}(JZWDw6`*($ammMYKw$f@2UUetT8i`Vt2&SwaGuL@#7h>4wqwp&h?g}qt>>2GLdo#>T38tsF*Q0?)qB4TVa7QX7Lq!jL-6$=>NMdzaX#Lkn1}-)Si;yi zC8NE(T{vcQ+zsP`Tek|lJ>W+&8dsY+Qf4TGb009XSaYfyaTLRjy4bId970aDa=*e5 zWdWs+Qc?j6_CJj%J9-XVfR1xH_2wnA333%jb&Km(R_m~NdxZ{s&H6L2_ZGih@DZ&M z8E;C|!V1Xq*IJY4Ufjk$O$X?EvbF zef2GGwk=QpCQ)Nkh344Ka>u-6_owwwDmF(igfu-xSi@rg%7GII%{ZlhQWq9BHZBcqv_7#+ zP0c(uYEwV#^KNOOQ%>)gw|(YumYT4@d0PCif(w$xou^HRjIo(6x+b~o`waEodTAws z^o|Y=dj|)e4WmbgHEQ|4UAdH7fc?J$&ZuhuX;nUp3dG~I)6n79m}liv9{Ce68*V_0 z8{|3Gw`sUI?H<734cY=)A?vB{9`e2=aqCT|#t-*t)K14jhye#)qrfTS$z-RluM0#m z&9+6Eb|?D4j=R3TKJbNzjlFagzxHs&lWU-RoDZWYoCfkDJ#tAx!~6e)n2B0gXuhR^ z;S;2zkO?IrE>1lJ{E%(`a#4{*cU{i<*8H{l-s-qBMC)5-J(r6BJL<7q>iAsa6374v zcJsE<6b~SBEZ7UQdu*q69Meg#g?q}IaN6x0K0Q^FB^>;`paPGO)|>dqo172k!~?s8 zHJywnMo@$_oJNBcyPocida700lWsOM4}3qFdUti@dZ~&xH6cOPSm8gy#LNtFDI!xN zghG-sGFceUPxn)Hra^VrtnTgRR_1=~y5`2HSJ~-zCjyzPAzpvRjq%jid5LSM6u7&K ze>Ra+S8U~Y;0`r#}9w{4_FS9FK$VP!kW%1$nUBQ2$O{Fhx&ws?FaSi+uJ3{ zwe9s{3;N#8sJtVr9``c;t1;!w!`V)k%c<#{?oS`S{F=Z={x?2py>@Tk<7q&E& z-FBu8WVC_bE@7+FfkG1-4|uCnPB|Y1!G#1rnVYxpzH-&&K|mJ|SVfla543CQMAHW3g2U+!zKP7N=1 z_D+MvCSMJuvfouolk5FarS$mmW6(HJ-w#mD@b$4`YHsXKLVo97M7>S#<0FEEW=M;t z^9212pMo&syleE2j7RWcmLp?oz)euqC$sdRdh%HzwqMMLJoEV-GZq+ucrH_Pjddns zF)k&^B%f+SNM2tVq)XFuAi|wr*HlGM(HbNv(|7s)r_6v z6!~Sfni@6m1#B|oX0RNz710MN|Ka{YTCWdf(#MF1#fb^XWE~ylfZ7D)OME;3JZkJh z&uP@yAIq%r&tG1RafDCn1tG?NOHmsxj4U_a{Glc~uWt8eh8j&78!ySjmoGaQ@`QyT zA57fD1kA*f${+)?!o|50n&c{6g zp3kl5x09(!Kl|kY-NRAHS?douIa&~TyRx%1vOB7FqEW?Ol3aXKh?QH2dk1PSG60W; zOt>RrT*lw`Ywq_4`U%@_N<9IqK~=*Pe08AXk}h!G zZVixTXLmvG9$R>pTk;qh~IAP zFJ~cntx5|$V#U2y_X9Kb^~aEf%W{yiFO9IKX%01??Oa2SZpZ>|gix5^>C(v{^NDH< z4z8mWh+9BX1p4VbnN`glp?@H84CKDxcTR!nj{@pJ|6qeJ%?=X1*W~f=_Ktu{&t(35 z;~VI?(y^|c>Lz53&CQ2q$A9eY?a_{uLiX@{^EPPYSIbXEcWB5ezt-#I2&@++(E=L; zpRvf>`ze4*mhA$Q%hQU&x5IoA8IF&)&UPnZIds0HMVLYo_aE=QfPu4;ErJrvVvo@g zM}x{Y>a2drd7N!FFzgt^+qUSkw6~64je%HSe)QIy5Tt&XnwWTA^5V7}uuecZr1zaz zu2xRQ74LArJ~1dHb7H&X9(!iz!;7z)I$q%>ob0#RNAe@=1BU{5D#Al8!aHw-R=K@nIu>*p%vVd4jOH*V zuDO?*n=5#@qaj|QZc{BfIVmG3c#_Fqf9uu=w`FFd86Rf6T1NOJ()z&Gl2N10)vwlhlunDV`@I5e5BjCigp%WRvL!=6XNGhI>s`kM&VqvkB* z`T3&uC?1`?S1Afg37O>%>~^~=g1A9XPtTL`oxy7KN*M1EsRz;|Z{XBgAXekvc+T6- zORw%fPxI^md%xdfoR-JG8xD5$I;U-nxE99#&qA$J=pS$#?+Vh zV_mUE99-$$Bdsn?->n1@rTD*sMb?CQ*$$*;*mdxK2@5oH74v`oz`kX!(u)03NqSt| z#={N&`?IX-c0o_7*R{)fz98HK%4c9N@gfIo@X)ijJOldGvosmnfFKM=57>+3Kfm;HOKNK>g#bqDEN*#gBg!57dmfOcdYrsx4g|8z&Va0#Wg?i|!itc|4=@KR;n-sO&}B z6^mw}lhj;sDWqY$)+h0sM!~luO!b_lEk}YP7jXr7PwqDJ4<#;%=+*-0UI=`4~K#n8>nM9`0~M#WE! z5$Jcno^vg;=EBkdEzWN8eeJB9U(X_kNmKqa60l_E)1PoC5Bvo${113G9wIrjwZM99C!(vm5pIR&YaLbdH-Rx;>Rn^nGOf1aUvemB% z`L^|%74h+Pw|q~t7gW>FiPTaR;-+!7I=W6WlsrnGKWa&rZPeGX0L~hM{`|a$r?5ym z4RvfkN|5v78o;xxZ`(MZDk>BsNJn5fQVAEI4^xm0)+t znQ+`o?g)2x?S&mmj#x~!yY{H&sELok>YKN4@_Cis+u=vuV`b!g&|p>w;X7q6{iCD? z-igxITMa}mx(9m>Xd7w>ohY)akM82QUgb%Y?HhO#;Z@tx6Rm$gEm4x1#{?-*gFpzkE06` ztF{M)Plu@RH;mvT(=Z}6Ka&VX4i>h{?{YkJMe7)19+WKfbESH2>=(7;U%oh_yzS)` z#_4L-G>})7e$?G6MM2g*loJBa>8s8^V!yuLjsSkRXtch0(YK_iaLpMm3oDC!hDy<+ z_|CQn!6!G<_XCxl$6^fZ+g7P~j`lXgnJFkxBSJh6?tRpPG)%|Yay3ZP!n`#{C^H+cTb%0?sA3i}Zda-ZK`jp20COB=m^S%#wnd!o=3 z)@zD6+};&Vw;zgokGgn{D`#J?DeTx~^6`@=dUEsGH{L?vqi>94$wI2jg)3FN>l?y6 z<~QQ}gijVK{6a7Xy+t@CNE>Ks5=Rhwi4fF$QdT1N_w(Z+0>k)WmmzgUk^OelzU!o< zeiGEn&VzTGrK1G2XIEV8%P!M7F?5eD)(oT;q zAqxL;V>4Uv!u}V1gOyR#NB17eR$W4tE58vmw6pz=sQefb6x9u`gsg=!+E(mD=PsPT z^uC&=Y&X2s03mL-ppQVLAam_U4wwqU-4WH*PFuh6hdO_0ZM_xf?$Kbx?IS@Q^uT0_ zjJc90Oj0i8lNNdhS1VKzH`^AyUtiZcNnDSsbbXtsI+PvYzg}~bxbZW+k9YSX85w$9 z&ZMF;s^ZY};17b^H52c5-`TAO2S$h?g>hTWBd8Xa9S<>&WI!tqgVOfD86!Twa_$NZ zWHbb}MG8p~HZVFGtt)yP0_3iCzWeeWnhbF1X8o$!IqkaM0eh)t&T^UXvW)dI-6BnM z$^L#;<(fJY>2MH4fEX;*C2PJ%h{doGg-OoDVAMjiUNl$^v@pHxG`dG=$0taX(o2~q zpA;yG;0l+L^Ozc7plfYuQ>of#LriG>q5b517)vl$)roPzU>64m7ze~T?Yvqd7q*3l zju3hrfh7VYTnd^`uc<^Us=SJj$QQd+j?d}nEK`sHVJi2nJtm7=(j!+s-@w&8#8hdZ zO%2HN2F;*a_hk-udglfP(+@sW07Iu_YWL{P8y1geC5~J}YwOXfl+-8jAn2r`^Ftjw zx#aEmMx7jD_O@xNLNl~;<5|MdqA?v4CQR>hLG6*p^|yRSLH_S3qF$%%L6xXkj$9`)4uA%EVt!;d47mdUg2t&6{IK$8$=i zQlrfcP4?e!mZ%GHTlFk(RZY5G#QUlU<{}RbQS;WSF`At{iJ?q3{?9-P)VP^HGVYZYl!b(3>I65K08`8k@cakKcS9ks1idbqw9fYC@ohFcC(^dnb}NtgOJ1IrE_r>|VQ;>N{GNNp|7lNv z${p~#aT>tH;DA!>h0WSqrRGCDzP0y*%^6>!&F^upiNfHX8^(?X&n@`>n)LbUtJ5DOLg^~{NSeFcRnaUPVxE6 z5fvEsQ+v6z3hw7wf4D;XBBAd=JL4|O{T%|)MPU;6hhI^MUy<^oN7@yw=}OIEHnVPE ztWX`zj3B-wa&|%|)vmO8URgD_V!({?&fTT!BY`n(ZBi+}UA!`wQ)bY{lY-$iyDLaH>AV7?Xf&K+z=ggJy$w!B8tre>AZt`{~^r@`3^ARtFlN3 zRIt96r>BQ|)dJ&JtRKeX7I98e*#+Upxv|+lj5r+8Xs*agzscgI$YB7Eb2r{@EA1cb zy+h%*GT+UAaaXXq`jVGKKA(%N9@0ZRNO`EoC;H3NJDg#yz7i2myd&?G;(Li@UjvA5 zz^w%|aFVQPxK?j@OZrmh#dSeM^?u`u)2=56IRO-|NCkD(K_2eD9DSqbA9Wp@3u%0X zd9v^3?TwzRmq~~e|JM2q-2P5ipgef-owkfz%W@{F^)x!GZ=OI2Op4_{8T%a1_qI~) zse4vdyI&fnU}#y)H;O#!YF6*UB`>cFgygZd@*Aj<8*vcP&>m*j{Jy6LaQ1h5+TaS} z8)^$~1Z4h6hyOmRrA&Y09ZXHK z7uV0nuj?&mI3&qL=@r?ip;o{xa*^9iv;38-iRwXZ4<{o0{$_CO*Vxvf!d=hQXG8{G z?_;K3^Tb+4!uW2KE=A4NWspZ9mH2EjU}Ix*zazkjSA$;~br?AS0XO-f8S6FY@qb-v z>^AghFe!jcD$K^a9PtDM1CPv2Qak*M{>^$Avtdaj|LzKUZ1h}FYqaCBTxzQY{@SM3 zeD`7Vb*$JsRE%A753GERID7L9M{{?-ek;dS58SupwZ^@pzs=%rtEy^JM;e0BWf|Qppt9Q*Pp(g)`tt$I&9q-uJwwJUdgiz zyB#Dh*WJ~wRX(*cqa^>ia}SD8Y03~z^DZRx1rqwcXf-A$r<-k3sG1k{nz}}?>vu`C zvu5;}5ZeR=Pyth!_jl(m&d>kO`{g^|PMQ@RWWSPmA5AXZJozjy^|&^(KW75XRFBCz=_t-ezh8y8&*SU^ zvih5ojbeUOm+PSN6ZLWD3HKHex#jC`4G9#?t}l4C%_gA9kUjKFQPXu@&O{>GL7qVc z%vlZ(q$Y{QMmQXGj8*pOsCd{)XD#Xnp}LECz-^_6d;VTsR#TX{$KQ@sLGP)h+3oBqqM%7ll*K6{fAwm|FL${fAmI=IYEgb z!xTjBlbA@W**k%rCF%5}Q!3D#gux1mfMUj=5V>yGV{;lxI%vtspIXrTj&NG~T&qb- z%dC_MC*npNECXxtXOvTWnO$)yirOi+z_aGI3^^=eS0I10;@j&wC|~EyPURl2!G_@N znX_Lw<|oc=!Z+@BUi}E$v6osxzH`wIA5eNBu*RPQ(Dk*(Ff&=8EdXt2? zr}~S<`P&twKCgT{Zgo*jD}X~~km^X1a{lw1OJpR0(P57B*5OHRV$WZ`j*z3cPT{yx zEt1yT=X?w1X8O2K_h;RcG1y`TTG!uM1Sfr=%94x{$i{&G=Lrt}dHlf6Pvl zkeI+#`e;?N6L&_3c=+zsz|0D&JGY5sVL@5(#TKV0o)!>bBYBBn&2PCItPdPug$9GH zzKZ?9r)RTl3E+609;sfSNO{l_C=Q-`nB9Y&(_8QzoOfdXXb)Le*-P(FH z4uO7pQ|DOwb&Tcf7%gcFAx(HBUoW033OR@Odwgd;F3nOP2V=n3g8F}K@c4T}yO{gp&%xur-G=_BapC<{ zDL9fq@mr$tDJgC&Hv^w(IUGDI8#$giq*J>~D&~lE*saB19S>QyFH0;v5I5*+8#&D9 zqtPnf8{J3dPq-;M+<-&MeDn*j=8hN(ExS(9hmubcARVuYOxfHH&SF4=M{WI6EsY_Q`&sz^x zzIEHB5$Ivz{N5_%vGa2bI_-VIr7;=#AcdTIDh``O!gdFD*GI%zq{X2k(jF;|;zf@y z?RyELkqy(i2HbsA1jz3kd%Og2Jo97y_n}GMnbK{zcq;*J+5&FI?M15Qdo3mSWNpz^ zzhC`rLPux8{C8sa?>adOh>Mpm!$RpU1DjSxXDs9}!8oQ4yAIDMVW|Q>J1{3n=y&nv z8QeYDh=6xMsU)?3U z9)jB#;^PGG@`nsy3-KR5m*H5!uUNJYECz;>eK$ikb@Gr0b=@`b;u-d4dGlGz;HUfj z9$3Z1lZXzZ`)RaXs=Ca$<9{CV_0z%S_tdCCX{JD8BH>|+fBBS#cM7w%SrlnCwm>Ia z&f_MF;9KobS1Z=Z6>)_GUdMf?2MyaaUR2m?ynOj}?3bg?J-An*E<3@Mc*+#i0}*rM z+8Iqxkv&m-%Og33^fyoYh>3uXZ2O!|CA^Ns^5x z%FKyc#Ax3XM;zEpCW`We3hMGzy$<_nfBqkDF#Xe${omcG`k#NJpJPoF^5F{^&NC0| z*{Lq;q!29Qby$g8XT}nh62K*%ySp}Y4$cAJ#nU_Q^!4|hv;XC- zu_U_W@7;D=sXsbA{`~yTwM$4H4MoR5DvvJ{?1c0Xft7A}Z;_(qKqMUH@~&?#myYkW zPQ$ZRh2M7P+F-iU+sr`&c5MFg?^iwaFY&-zC>C?|Cf)Q7d*^M-k`w){`uKi^A$Kfe z>CB=RY&PP(mX`RPn50uG+TXpwfSt;=*Y~{fwZ);cmZ7Eu=A%Jc>VJEG+^ez(0s8el zQ6mG~jnaTj!F)$$SF28_a!+4v|H$so2F6?t{svDc-h{0qL@d5>QS zMsXbmKWkY$TSv&4!XB@^oz#A2)yTzf>)`Ms4!wHh*sAJP+ciC;!lXw=fG^G5OQt|E zRKe8NrmsxCMLMLodwDaMg`Bi}$JV2^ZoHMy{InZ_ho=#vnlV~tiHSgwd5DnO^b_(% zMULuV)@HqoE);YBJRf5-T&%`RuzkHkIw%$22}{q}v@GRN76U{$r&VG>F!iRkUMA{b$%}126--`tzs4ONY^~@Q zz93{^U{kA9k58c$=AIYMsuV{K@mL~Yhc;jctms9JBMs|5*lm6?dyu7ikFxIdeGqgQ5M$%OP$XihMSkZfgNvlu8L)1INxUzg89LQMaWc;x1N{C)FOjcYLN2*K%pn zll~l;S3dI7wdw8lXM*lVHBZiZB)`zr%-*0T+As5{O{G_wKU8unJTil_~?f6!Ms+>WVYB5)dbnN0#Ucv3f$@sD( zl!+f2Q~I*NW-`8Fm5O%D-1?D;hcx5$x^s-vh{C7l&U&r2?UsIy7?i#EBQByKV;1C8 z+O-TRMp@%l3*v7}uE(ob%8I>NeBbd`KD3X`vpLhnnXF5ag&H(Uc`7)ny1sI>B-3iH zwtBiw^?UIK)zPag=pS0gNG=_JUBDNO7_p#7Y<#*X96lsrnad{pF^OxyVtMwYEj<|y zE0#-Teg3uRX-5F>VShglt3@{*<^k;}`edSIX{mO16g@H3$TRBd<_@g|i^3PtDhGOg zw1OP>@9(!(YL#q!{rm*CA@3)g**mZ4JRO6Z<5yG}$R!kdrLfoVgfJl?scNe!4Og?Y zl)kF$eE-$MlTY1-sXdBF1vyodd*l|y_9PcW`-=>f<};96z2msmnfmQ3&m-BoL@(Uw zlGv__VVvgS56_q(Q|Ki{q;{!Ea2kl+T3u?fzf0HNo31vDd6|5e1D;!MM)^#J%zk8i z!7@|ErH4-$ws-dVO1>Huq+M1*F!hFC4h~o6vwZkk{f>i~lu5$qVB08=rK|K}7`$ zqm|;vjMG~bpk8GIYhR%XG-j)j!Pn=0UY?hPI9g;^oY!UMgGG^Z!T;CTTLwhcg>9pD zpooAdjSM9%T@u33q0)^s(mj%+A~3W_cgK*@4Jt8o!_Y$yHA6QHXY)MI`|G^teBXv2 zFl_d$*?Zk<-ErO56-4kFtX{-L99gOg2CwIw={WfkFnp)(FHm3~(!uogML&I!-Du<9kg56Xrq+LCURa-*a6d;K57va}XK-)~hQ5u8mD1m`xBn@fUoS73g`!Ry(Jn!yH3Wn}yeq80n`m7)wt-z;eFax7r0SWQ6SM43 zaVg0tFAp{XxOqO3uI|cGdmJnTQrWbFB3sh7!{+Qg^SC~FKPP&m4Vv<()90|rL!8a> zzm~8m$H=i?F$$q(CMunc4ToziWvF_KJRP*$bN*OYx%8|a5IveKq|WpJ-8>)feU&m< z4XR6*Gv(L8M1*6-vi;8vDQMbrr!V->X)f)cq8J`JRRXe6%`6Km3jPUoO$6So-e; zVdEN1^%ZNI*np*ECX{z9ace7i*EEWffw&`_F4LvNK;{V?9v6sD#X^J( zy}yYn1qj?5vI=@WtpiB$zP>&e90H=TTcb!6;t)FBQ>_{Er+y)srAi$&tEdo;eVNw} zg2Tp=HitW5js&^p0zP)yF+2%BXhFqwtJDh3cuw!G>dE*{E|zBMqJJ9AvPu|c)GTX| zQ(Xlo)owie^6#dQTeTQSa}S^Lq+5|Q97f}d#?*+P?fh&yKe9A4=d0fxutBnr1lJgP z`J4=95F;YI)@iQZ7mg)2G2URiS`7K)cdzO2Y}1rDD&R3WuU&Wa6>c-e*U1SS9Tg=D z-e2v-jO4vD1A~!P^-M$!;_*+p%L6T2KCYzr-7>vJlDE$X&gPl!c3s_VYBIe|GUV$V z`9;2~6pLj@ndp(F|9F0v6o2o)B8<(>}fVnpFubomEImQo$o(?^2nD3?xo_t zo7E)5sbu>}vq&pu+{~qDIKlR6Ht}?)a}b5yHp;Ci64%qst$1Y6*!)koi-zZBlnp291&ynbH%t|jKdQ$`Vgt#JT~fQDZ2Z^seiq5RMY^VO$AFFU z0`{xSjV~=-tpZ&$oDcOA4E~q~gt5ZBUoOjak*r!pe z-!``BGb7TVpD^4^Eqn;yVj#Ys^SXo&xQx=VZ%%&Kxu99cHU{U@y|#6Jq!~GGwLRp0 zBoTKV{{aCi2&+(oPW9~`85#NbI$6+dZ)Bha;Q6$A{)OXTxukUIxyv0X-dn$wZ}H4t zdzAkxKJEYL!3}y$7HNWjDAx+Fnb1}l1@9{1kYqJ5wc6)+{LlU_%vsp31XlH*R!)t+ z11eKrShe~gGpRua22;-QA@86^R|iQJj>@mc>_K*@n<2;c3@?V-&(5h zz#^{yIJ2(L!XFE#fLYA_Z^e?N0}{$Nw%n8Yta&IRBZaSXEo<+SyR0Nwf!BYB{cC=} zkN8p<>WoRk(sQ*tfZv%g2+I z1P^iVKhWeL9?~~Ec?pJzj{~%H02QG`-H?Cg^Nkzq&P^N)1&~{qxw_GR5Q5hqu-%P7 z4*y-QR4dVcgW-j%-OUbQ8h>kiJ~=cF7rF;xhK zX^;2y$O3Y}3`CU_`Vfn#F56L?ZViFOF38lE(H;6}QMPLb%M}!=y(+e%bBF#*z<04( z;2Zq4X_Ugy0V~y3F7|fz>5ugNr;79@L14C+IFjWVZA)J%GkSD|9V+|U6bFMk@AXT_ z+_DN7%uDh|kNE!CzG z`_fRY9S%ZA!w3H7dDqLySeK%&lefU(Sh1-<1~*70{P*ttR>n+O z&|?EW8^*6+BVTXKuZ|g$1=+Zk;Y{N^*5kuJyW1t5U(em^zcG&8Bnv7$MgSEV>=qdK!i2_;f z8wv$9KdjY$qzuRbmi#L6VLMTVb}u&{58VXDy|CSukD{0{Ys`c(9A*qN?i$KjCW6Pe zTq)^IXZ*X|-EbWF6cO6ScV;B^GAPG6Qi+2|Ugs@cR7V*bYph&55p!MStVK=yV@AI@ zYyPLLz!z5uz(z^S2Bhtb0nNWJ9Of2AEkKlc^fRPHcOw7$g8|?dO}RUm&kA@!9}%X0 zORaAbr(X8zodF*BApyRAMgA2uSLTeltxv0Vsq7CBdRCo!(@VN945mMQw~m5Z;}SRgNz6%^jMEq)*F zD^9S>R_y}*X=6^bXfA=CfuY)qH3*#9fNgYHi4dorXc0f1{{vi3*A?Z%ty3s&bg>WM zA&O^rH#u7_&U!dENpL%}kTg!Cxm6~Tc^b!!wxEU9fLZPB37dWf3a2WmEx`t5`qAVAsO_i=Z%*%?z-lQbeR5Bhe7km_u0)1zoSS@36HSDR8H)_yDHaencv77tBi&gzrDuY0rQiG!@V zqq~fJ4>j&nnV*MKi9oQf9A(-id#lLx11)ZDK|PmkF1R`ndIxeCHby5UsqObyPL`ON zt+}2Zi9*Y?%P*qLFVBxrd&pQLsDWu!VpgVSUyE0BM8tsCc1uK>L&_uytKdv-fL;Ta zPY1~@EYL-=PHDp5)9>v(e7#ZWsmy?wB#T|yfG|2i^WMQV2FR}sr6kYM0gx6r=|XXH z#;n1tv~=6?FI^9wpZkd$ZLMW}?vdtHf(aUB`SxFVmQgj69;OEg7Z&m_*=Uu_op3HY>HSf^MyQl zauEHH=n41;F{#yjJv zN9r`3x|jwVG@bV0Q-zD#`AM+<*7~=Nh~rgtvLZw053L22jMHfZk57(cLtAw;`YMNq zv#}$lmoO{9-h-^XtWO&n%7gM9e(g0I|7p6l{G5WSOB3q*TFcZndt^RVTf9zgXSZ2j zR%VgG5{S-%M4Ps$iAXn?;rvraxmrbns+|sRGUARO5re3WlFoKIf4qX$DcI<^Wm=(E zf^++W9TtFzzD-W~bQ$onI}UpBT7ge+``fJdvrO)km5(wj1PHvo>iXk>N>Y^0<%N_e zy=Ta!YS~d%Q{AJ0yL@NfA{B5oMjN;KE8H=ZLZB#(+dSAsiCpmG@BORK9BhYyO7%KQ_=PpT zAogC|MTAp|9sw6Zipay&xgF*jTX0hwopNuARcfGDzP^)y#KwAFD!Hh2v^uECRp>D4 zU0hyv_D&%fG&G`^R&PF~ZW3?M07(<{_*+L51m4^E8OZo9bqlkS5*K$hhBVrqsLQeo zTgNP2o@7PfQ@v?JBJ(Phs=SV|oXr>AX570rvJvE$gHi-CVDLoIIKX0D+I(t_YEF!b z>&`B1ych@~W&$LO*r9f(uBVp#Nn!QhTXv758F`3$@MgSk-T8a;@C7Jnbe8G8n&IC` z%;TQD5#d^QMklJ2=qa2MfB<|?Z=$0=JIXg{%il0&!Jjnv?1sTGT~@u1hu{?^Wzy$@ zgi{%>W}jg-~oy&*ah&m0HZFLqju*X;Jf< zIUl)Om_k8=@j>mRJ48KFRdiK=zvhr*eo-cEy+mij;p^+wd)`o?hF1`juVh0DGRm>C zQwUP9agTanal8+`8eeM{H~KZ|dzePOS+nnaI_w5l2%3-S5~Ze?#avZe!trARG~}g= zY$*8W6i`&mKORiV8J)IN>(hd=CRMYxX#Jz#=N9TL*eKqThtwXo> zm*{}}lX40P>Mze(Ik05GxwY}1Cj}~5l~1(DbL4Yte-ovL5IouY5R96NYp4{|SMGvBb#qv?JQa_F$Uxu?MjoL#YwH^D zNncCQ#;u^yvwTq8K%wU4G4x8reNm7wCN{czdC&8_KS>0#zsi2h-i%USKRVKL5W9+i zHXiJW2fcdzY9}f}OxSI;mnqGA1G|7*+~@2h2_r}k4hxIwF*_%qwAz?_=>K;>>~a)= zIBt!I)zH+K^+8hso&?wm6xmuN`s9Y@pQ_+*dxIUAtAIaLXN_XbzH-@M&&Y>1ke z;A}o);o)&PSQ~O^I+c+&ZwI8tC`5lqymGXc0TDgeXkPU6_)L!95h1Rl`9Wuro%i7D z`HS=OyBGUoTCi7w^e3~Bxw&6XOR_XjwxqDATz0M|EWuuHvku9d@2fjOh z?mxoAgPva=C!K4_(%r_uPI_RZkhB|`6RA|MB_lg$5k>}@O78t!Hw{Ie{BAgVSZOB! z*9UUM59&F-N&v-J?Tp{~$Sl4a^{K#0ug4inIMnr~uX{=Q7!g<^WRNLPcah;b_xnae zikFj=a9Rj=_kj_LOr$Jg;H-iLr0dbhp_$S-;(8pZvVqxmbG}K)PD<{|CVSb{tPfJ? zLdr>hYq)=(-;Hs1A=T$%1z4v0$UY1dH5BoW>ud33CwaO1mlfT8!5nm_cXOJj@IVuE zw&93_>@RuVf7SouQ`YTgKxz>+5WSZ~RgC%cK|>Fgui(HwG^&B?@3%$l?dtc{w_n~5 zsHf?HPv&Tw>iWKnFfvS1-J4j$K8`X+lb3eus1;{(O&wSyjdciZpGZiqk;Gt8NjxWk zLL>pD*wA&FMfcqFdc!I)g%8FWF67gj zEHwb8ner-;5G}wx%!&qo$g-3Ms}i0=r+KLSHSQH=@%2BqRxDt5=wGX|bqJp`IgWm~ zP5P{sKPMi_^mHhrm&G?@C{d&sD8Sa$)mo8Dr4TZU8sps5!Md(KviFyj_G z9aZy&fOYCN_E*w`-1Yb7{q7S`dF~h21g%Y!nQ|xC?p_`XxTJf%nDg5H>qSMtY1rsG z@b@lQT}S6|>Z%Dhbf0Exy2SyTT$gCwCrjfq>Uca6KM@kgQKH+F2>m=i|JFAnJ3Ai6 z0_Yy$g(M2Q8XNx+*>2L?moYdU*}pou-};&o!JZ7j((=4E*7MjccmS&^AXoYEd;j>D z4k)ZJ5^(WH&=}{HOOS48ehQd#fx>N!Lkul z=kDzSH*q&F!s&>Kh-5-PFLp5zP>J=H0y3dKCmDg%`sLX}xAE7+KOXm=nZ5v$^4*+f zW){M!>u|Na;NXxr=w26vLvzN+W>!`vu*EfOw+x6+nj*r5kUyMP50V_3rNQ@~Y;6-0 z^Z3rxmJmiwO~s6YWfld)E4)tbRXfNmK9n}e$%`l{NUn%dR0W}Lex{0vp4#&UHX@0a zzou;&qq^oaglRIUHDCUaqrdf4f-N@v-NW=vp_n`;wzU4j*a(Z(CiDKRRam~mkG;<& z0RwX2Xf@;BlGg|Ls(V~r|F5*R6$2>pOV~k-!$cG&A1xsxt;W@*#^LQ`a#GKe12tef zcT_1wS{``g26+A2v4u}tgm^Y`!q$PtgHvnbv`83GCSnBk6&MUyxhobgs1+WFCTqm# zX4>F_6Kh|t82H_$^ERVar6mmVx6Kc1+YMW9RO@*LR|ef&VkXwuVAm3@$<>9^)`177 zjds_+>dVTz`)6cNQLb=0kVcW3)aF`xJICSuZOqy1j}}xoGK7Bym3aYadBUjv7vuO& zY~%44(S3d2wDY;fLIk2b&y^zfA34W$O0#j%e@u8u@JS{?j9-rV`LAi!9)|;h&QZ8p zPY>&fex<{eSc;*zdAAwP7*$&fr~`@w*iwml7S-3U-X#iQ7#eEwHP}zgN(HnI`Dmi} z?dBRi_AL-n#U)s`$rk8U+9tB)up>z_!*SP-fc$Y-ub}g~>INW(j2j)!IX}XRLKeP_ zO$ASisH*ml`oQq^q(PB2H4wmuQ&LJ>UT*caeVG}>^sZn`BW(P=i^yPZiwgmzphS-9 zE&ys`d)_nGbk3*}F9&YQ^pdClY0%&R@D8fZQ+TV0CJn6sl{8+p*^1txf>v{z_DqG2m-`coN-a zP$<%0&CD3o2L2v8TD8li%@|i9d2^N&dVc!`2-J`QlbiRnc|hCxG;)@i$*Rvy{P!3m zE8%J9=9SEQg}X8L;v5V}JVR7L^uUbsv9XBmx7CZ(f1k~jRCFXO3n2zd9($S($O5_# z-9%#76GMqjK}|W5N9bsirmmyKjspS<*()#W<-(-WwmU4nn&)?s6XVaCM%wJXpDzxW zp;w&i;(;VGWcv4w;;w4Cs*9v$Z?vKn<=@6^hk;Pxc%8$!k}U;*Hv}Eqx6bf<96xPD zi0_5bSMAS-Mk9Tds#2*$w5@}!bPeqC58O)ryqoN7A2io>`JxwPq;P`FAAXPABG2lx zM$aU#dO%V`1}xBRef<-K?)cxMyw1f$tIs!xrStX*3&X}=60FhNY0hsPjLjk(i&NO(b3UF9~|HB_&<6UzvV@8^s4oBp9%{psPlEz3`)t|R0ix< zLla7y_XZp^iv$6UMBS{14>3o(fiwUH57f=s33Xl{Za+p{;k>q;DIq623y?XVy!_hL zcaO=HXWM-(Ds*Oy<>z&+<4Up^qAM4*$JA~`BP?enKTh3)ZGQn><-z`dDpM*e+JV(63A!8 zF=H6Up6`jD_=84RB(!tx7M+V#KLSKgk>Az0Bj%K7~a0yWWP%$G`SX27~I{ zj*9dw2b?)*M6H(rx}ctJbu2cue+AHAzL50Lxr=bZQe(q401%v=7(QHmZ#aZAJI5eA zEGR+9bTuH1f_xTW#^AFVF9H~J5?>oHjwr@y78Ew0t|qnY1FbLWbB2Z-G~NyaGBu5k z3ypIhNqIb7)uLFfc*$vK;*!iyPfqg3$Z@X#&1mc=-dJ8+)-ahY7B$g}PP#*u(akCD zp`PWgDEF zGfyYjRv)AMf%DlWu7=OlEZZ`=m5-|OpQ zt0;-d+p4ZaHDjfXb)PW|{JMQIcG#P0l3 zKFxQ7`5i$7B2ByA`f$TC&4=;T>-2;$KwuJCq~o%%*bduy04mol0=GfpE63YI;$%YQ zo(9<*Q3*Px2L@VZ=>bjEDBp9tjZx;^)b!A4%p5Tj67<>vD~Y|a#E z#fzD&fb7Ti5fTy_oN0k6cs=EbuD%c}nz1L~L<%gk-@j4<1ST=Z;+{KYIT7OL7X?Ph zME2*A?@um96q}kFOLhIUL8dx4ox*0VVr`a3r|vS|xqo9#mF3{tYHJAaT%#TiENdxB zZ;g_+NCEJlg*$mh#N~Wfr%vgwy%&FICKkXor`XC{=|HS+8rSrzV;x@=Kk= zXF2&Q4+N0LuP++bq9_Iu$50nuLT_hiKR z+SN6oFjot`g#I_pHITY~+cGB|l7Q%ojZ>*_j7NKuUODCOzFd4Iq_waKX%6En>h`eP zFOF6-PaYc(FUx~OPUZ9KDDtjy5};dz=r*7uAV{u;RhXuDj+s^bbeg3G{*rRt+}=@t zkrw0e$zr&e9%oFP6+j}g0DCa-c6J6`A}9qGr)KqbQ;2xM2;OQ^9qLo8x6BYYvR`k#gwMZ4meEblqchvs#YG8=Do?~*M*8{G&)-SQg5T~Q+;%|cOHw~94IJf zeasH8m7Lqtyt6p=BAdB&)8`} zMrn)AF0{18cW)U!>*e0w(ZQY!eYYPN0x;FR_HA_OilmdR6rP2hoJ_CI9xWK?> z)L&E(?$Y7*x3Abf4K`e_e{1*w6_x`9V~M!Sv%}5l<1l51r;(a@tQs+M&OMdf8VQpo z0MIyRg&olBjH&5QJp@9NBHgO)*4E3&KpKD~YO%IMoG{G?CvB_LTOJ`k*J!>UM4V^K zuc)qGs>5T6t2!(Nvd`O-4d}g6BAHLrFTH1uur7#@HLWIw{ zAutr<4xPQ-$!|RNzV8xK6LuxC6XFwwLu9RPF=+=_8>)P$b{Al$SGUm6^Ku!)c~=wn*6K_ODGMd&%@Y6@hVfl zlm%@cyQ=r;cqO=2Vr9v+-lO~bh0pi?XX@hTh!tP}Y4Ui)c=?r6-rS}2=|MUzQ5B;R zrOHh%@8=tFI4rku)sTC2`dJrBwF>ZXF$4J*Etmu#F{Q9YrejxW|E5&eDO==lqcm!U=jCX;0@Q5GCA10>4$4dme*wflHAndIqtmhu)kd#|_bOggXRr2u z1S%nHJ@!+LIfkbl%1PS^;?Rp)r%eId&lg=xX;AFsv_nI2ZT1NeO*UEL48r>Was|xg zpTG#wqfXW<*iM&=Uxa)GSSMYYGieS&*W zc2JZ~fU~oUCa!WX&Irl?DjYjoTZ$0B936Qlx|2j;Rg-&&fs9&L`OtSlh}XY#vS^oBhTEyn$|C9 zX=$M)ddC2nBbDIi8^ya2*8qscrdL#L0MD0w19|uGsLp2!@6rIeozKTuluunUtXX?^ zwzQ?#ri;T`BXZ2dSSJ8R8_>Bcp1PTpyEV1LO8239*>O$z+h!{Y$t3uCu%|TzrqI(p z!p+0}d{Y_40+f1@DB=il-?M{+?t*Un3|ay<-`4^HoTqZ!i7TtEd^O*eaI5t5kI*P- zutkVD>W>urjP$EmUG~*_mF$jp#k0cEPow6~U>Fk2ww6m+X-isZL1Sa(RzS6SWsJ= z?e8T-6`3`~8ZC@vVfeRBrC^y_dqF=1P|@r`9h%=dWRleA;~_$5 zcV;tPKA;N`;+XF2dm~ihi?gXo0CG=pLsyMEqjP?;N7TZ@L-!N8Ik?`i9fWLa^6AI$ za*KUZ_cruh2-dKn^pENYr+$h=r@U*n#(edZm7Wu_?4Y1w6VS8YnvSaZDo0N#WSg(e zhW8K;fF0K2(rDUDKIj9K1d*aA8qXDH%A!XEFz^1sdvAu?yp?AK0!Rh|Hc+NtU_@if z*2p-ve>6lMzae6(O6p;gycUw3w^mkLpt2Rk^~-@W4GLVXHFSAin}l$@4~0ZCegZgR z0E9t^Qv%o5!b@lgKETgQ^P=!aPtY3BCvV#WA9b~MQn4J~c|6@$wWhrI%%q3P&(7(O zK7kQqg_2|Q`wI*{k5TZX35P~Zggj|#LR8>@{4@;w7lsW(_p(&!(i82{#CvAa(Y=OW zL;ZjAp}7}JeB}!8nJFzD>>@&H;$F18*Pe;>T6A*v>U&scm^wQDW;#y|-fBve_msjY z>Ca()-BbU~1slwQO~|8I(S5IJ7Bf;tr{$fSKBm!l(i$;mox9P*mK)gh&2_Ap7*~eu z=XfxE(R-Wl6v4hyDse_ymh!-wxP~sXt}f1rX0Mjq_G6uC=*h(FzJ)*JQCGjsvhR*_ zg6;6c(2yuyo20REkTtSdO6Zg?>7lm!V(rjvle4F-`i;q{DM{PI2a?9aohQv)P0#QE z%8RjPDOrx}+?tHHZ$xKgA5(R5hqWlXJi;3=X!Q0G2O@W%%@x@rsbPKihYsO(?>3?T zoqJDccqNvnda_5zu5m**N?K$(rYDP^e@%D$Ao;S0j1L)*`U+G&S)`mi9X6R{R_6kv zqZ<=VqHbPof4E2zIQ@QMbXVT*UK>W-yYg4QpfI=dS@bSj54EC@6j;H3@vA>1zBiW2 zi$ERD=i5AIZ_fvba8_f9ibzwpDacWkzev9sf=xYMM~YAB9^vJfn~^+^3nvqLa~^kA z(`*9pW>M9!knCZ+o&dhl$)DRVW`r!;yNyB=6oRZ4Cx)cXj%Vw(9pMUNoYe#0+amkV zfeGws^>KbqN)}jNvgo{elZ2G>u9^&^tsgebV$qv)F}z}KK5sv888`_08IK$pwBEya z#Hu-)(SulCafm@r_kK4!g;#k!p_b7C@C85%;sP(iAZrr<9#m*7f^EQQX$Df!JWRH>enT!k_^i1O>R6#qks^* zV|#)J)MyDld1r%b{29|%s!`AUQ|8H4^C6eKTJdb7-7$LwnhK4pZ(ifPdo%j66>Cu57^h}C^LSQ7<>|0Y7RVk zE**3I?+#fjcKOPTkwlRyMlTqubVUifvbi%A>XZt`HpsCnn@deQ;d-1omPJ=Kftqqu^Uh+ba22#OxLAcLN+C!N{{HEQs%ordWp%y1VuBKo zS2y%Ha>{#d{QcHfbr64IubovwFw0@CT?3Uo9AGDg8qxT&IHpR{53Q_Zhg@aBoNx1vS*9hDe4)yz^@*2K7TBpI{cfiq zk2WUhcm*8+gEv^udnlnus>&aH%PT|bVY<^}9oD@=StM_TyqkF)nB|2cee>HB%C}4= zTnW4@1r(dfRM2u$(g3=Cw;3uauuO5i1{WM-`Pb=QapKa}V>(x|E5xXQ=ol9Iacf73 z@oWX$8cxSpSY)>^u>9iLG{)Umor1FjQVGOyrTKt`Zq4L(I-2`U<^W*&Nq7ijy=6qT za;fjj)6b>()L(D>>2wE(ae4e~G*qpJZXlQOH1EwyD?L>*x&`Eik-b>hEL z*()SZrW;-*E-aR;29n&I`CdeF8W7Sjx@pwV9oT8FMY3!)7Gv<~3-Qb#8)KBF0i30? z?rndKG)h`1+*D`Wg3l;POt0dtWsROrfZeXviaTjMa@P;*YB4#|lJtUUV)0w;10%*l zU@Ok|C+k8YMHuTj2z4;ym8?2Us{LW|r2Vgzd_Tx6yRB#ws<$)B(CG|`Z3KLiHsjKa$+iA-4t9~f(vm+Xi{C#iimVO(3tq0!~!udvR z0R?v8=!O7&c;yBjC%njC^eL+J?DSkiyyobodxJq<34J)s9M<6ePUW-RlacD?tlGID zr=~0_fi^WJ!}}ma#<3#0R!(s@Y1AY-eKA2kin2?SINgRdQs`PGr`9${+^ zsl^R!5`gQpM8jHy2xTwqf=L4ef#S`K_ibqtdD-N;$#kS<0l<2wQ*u<>Hri2{=y61f z6B;*T3P=q4(7=DuEf>u$bikLwdCT77dB~U(Vxp+=LZNEP!%kP`Im*YZOj`s*ca2Dw zD16QS%9J#gP9=+_T5lTq&7`nh-QdGtLd5H>v4QNSi17G3$iVhsk$=vf$p^*}P8UEL zz_3`*9c){Dq@QgQ_TJSj-x20sM}NzU5F8X61xUD{?s)THhly* ziDsf>EWDO(QIXU{r{6*??s@HL<-nRq)CVI2!|tbCY&}m~o%AuC!U&j%iLf9)E#sfV zg*W$r_H>{_^+}rJRAG6&2c>oCGf+5b?8?^BM!5-m*r4FRY?9xCw4tx0wB+@so41TJ z4ePb#o%N5c+#Ppjt$`( zI+Fi!)dqUj$Xa`{n!p5o*QlV8M6#5qp;kh~B%6$K%v%HIpK{VfWUNmgdKQ^lHveI`5%qmPS$Cs+t?&P4}19T0QryP$_hv#S5Q`j?I|3W!#e-^mk( zMN&kJ6eVITpfJ(nIq)plEx9O)E!47zIUFgS+PEGskxHZGB>AewwmNnQHtT znZk^<1I(?@e!^Ttm0IOSKe{)BugMe)#m1YJ#Rti;MZRQqGcJ;N87l{L$V&fnA8O&R z!`SM?EXZOheZ=?*Nu3WK?D9zL_$&3{1wh{)N6^%2jckKC#<==jrgz(mmKZDg{IlFc z6zs@cExD5XB{L*m{^(lyOzdU{!1itVGpMddX>VnhK-`G)hCGf`#Aj=r5JC)!nB*T- zE3^p>2hZB08@`Y?^${YB#+FHuS`acKA!?)reQw$Q<_{t4XS+uJu5={8h?XJOR?hZT z%dBS7R@3ZyN_|N(prcZ#QhW33@uW~m^><@fp|Ya7o_d9VWsM7dVR2eIrFm7LIZRND zjw>ux88Q17j!_fT?zIS+3_Zrb9+*Vj(lBIHJy6x41Fo!aF#`cF^OoI0HyRTo76qKx zjVaF!idnm=Qr*8czq~&GhE?ePGYF-YR-2%M>a-C!9G?lfV%nmeb&wrDGtZEIj9syKmOdQmqurN?j5`Jcxm)z`{;l|zn&0#m8Wp< zxYRWO88~f3bdFW=ava>lJ3Zs$I`d7W7}+vptbrofpxEke@_#+38c2 zYA(Mz2sX+^Ke*fsDshql{b$-%B*<*AvOh!Iqga?tW^UngWPi36^lUra|C(|YIP9O> zrF3RQGLs~q!SuCWD~}EC7!m7@iP*P>j{X4#HTUllVfA$?1F}5o)#jAkL-ld9gsb3B zRV|h=_~gsz1p80nYKMPYdH_uR@HlI9{?I3i{lN03OX>v@kkF2ZC+XZRFc`_3Xi{|~A^rDZk)%yD}s-=b{6 z+F4k^RBee*lxuE`{}X24ZtUoPPRlxyQ*f=)W8y3S6tZE{N6qe$TRi)3Yw(R5IL16` k2DIF8$HPAfn>cSAjU_0zoXoi$b$#>XrB%Qsk{`eQKL8DDx&QzG diff --git a/integration-tests/multi-contract-caller/Cargo.toml b/integration-tests/multi-contract-caller/Cargo.toml deleted file mode 100644 index 8bcc084f248..00000000000 --- a/integration-tests/multi-contract-caller/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "multi-contract-caller" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -adder = { path = "adder", default-features = false, features = ["ink-as-dependency"] } -subber = { path = "subber", default-features = false, features = ["ink-as-dependency"] } -accumulator = { path = "accumulator", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "adder/std", - "subber/std", - "accumulator/std", -] -ink-as-dependency = [] -e2e-tests = [] - -[workspace] -members = [ - "accumulator", - "adder", - "subber", -] diff --git a/integration-tests/multi-contract-caller/README.md b/integration-tests/multi-contract-caller/README.md deleted file mode 100644 index dcde272202a..00000000000 --- a/integration-tests/multi-contract-caller/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# `MultiContractCaller` Smart Contract - -The `multi_contract_caller` smart contract is our showcase for executing other smart -contracts on-chain. - -It consists in total of 4 different smart contract: - -- `MultiContractCaller` (root): Calls either to the Adder or Subber smart contract -- `Adder`: Increases a value in the Accumulator smart contract -- `Subber`: Decreases a value in the Accumulator smart contract -- `Accumulator`: Owns a simple `i32` value that can be incremented or decremented - -In order to test this bundle of smart contracts you need to execute the -following steps. - -You can upload the contracts using our [Contracts UI](https://contracts-ui.substrate.io/). -If you want to test it locally, our [`substrate-contracts-node`](https://use.ink/getting-started/setup/#installing-the-substrate-smart-contracts-node) -is an easy way to get a local smart contract chain running. - -1. Compile all contracts using the `./build-all.sh` script. - You will receive the respective `.contract` bundles for all the smart contracts in the `target/ink/` folder: - * `target/ink/multi_contract_caller.contract` - * `target/ink/adder/adder.contract` - * `target/ink/subber/subber.contract` - * `target/ink/accumulator/accumulator.contract` -1. Upload the `.contract` bundle of Accumulator, Adder and Subber to the chain. -1. Note down the respective code hashes of the uploaded contracts. You can - copy the contract hashes [from the page of uploaded contracts](https://contracts-ui.substrate.io/):
- [Code Hashes Overview](https://contracts-ui.substrate.io/) -1. Instantiate the `MultiContractCaller` smart contract given all of the code hashes and a starting value. - Make sure the endowment is big enough (if you're using our `substrate-contracts-node` it's `1000000`). - The `MultiContractCaller` smart contract will take over the work of instantiating the other smart contracts for you. -1. Now you are able to run the operations provided by the `MultiContractCaller` smart contract. - Namely `get` to call to either the Adder or the Subber to either increase or decrease - the value stored in the Accumulator smart contract respectively and `switch` to switch the currently - called smart contract. - The initially called smart contract is the Adder. - -
- - > __Note:__
- > Depending on your Substrate version you might encounter [a bug with the pre-filled gas estimation of the UI](https://github.com/paritytech/substrate/issues/8693) - > and get the error `ExtrinsicFailed: OutOfGas`. - > As a workaround set the maximum allowed gas manually (e.g. to 5000). diff --git a/integration-tests/multi-contract-caller/accumulator/Cargo.toml b/integration-tests/multi-contract-caller/accumulator/Cargo.toml deleted file mode 100644 index e506880618d..00000000000 --- a/integration-tests/multi-contract-caller/accumulator/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "accumulator" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/multi-contract-caller/accumulator/lib.rs b/integration-tests/multi-contract-caller/accumulator/lib.rs deleted file mode 100644 index 8bc171a0af5..00000000000 --- a/integration-tests/multi-contract-caller/accumulator/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::accumulator::{ - Accumulator, - AccumulatorRef, -}; - -#[ink::contract] -pub mod accumulator { - /// Holds a simple `i32` value that can be incremented and decremented. - #[ink(storage)] - pub struct Accumulator { - value: i32, - } - - impl Accumulator { - /// Initializes the value to the initial value. - #[ink(constructor, payable)] - pub fn new(init_value: i32) -> Self { - Self { value: init_value } - } - - /// Mutates the internal value. - #[ink(message)] - pub fn inc(&mut self, by: i32) { - self.value = self.value.checked_add(by).unwrap(); - } - - /// Returns the current state. - #[ink(message)] - pub fn get(&self) -> i32 { - self.value - } - } -} diff --git a/integration-tests/multi-contract-caller/adder/Cargo.toml b/integration-tests/multi-contract-caller/adder/Cargo.toml deleted file mode 100644 index a0cb0ac47bf..00000000000 --- a/integration-tests/multi-contract-caller/adder/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "adder" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -accumulator = { path = "../accumulator", default-features = false, features = ["ink-as-dependency"] } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "accumulator/std", -] -ink-as-dependency = [] diff --git a/integration-tests/multi-contract-caller/adder/lib.rs b/integration-tests/multi-contract-caller/adder/lib.rs deleted file mode 100644 index 366693122d2..00000000000 --- a/integration-tests/multi-contract-caller/adder/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::adder::{ - Adder, - AdderRef, -}; - -#[ink::contract] -mod adder { - use accumulator::AccumulatorRef; - - /// Increments the underlying `accumulator` value. - #[ink(storage)] - pub struct Adder { - /// The `accumulator` to store the value. - accumulator: AccumulatorRef, - } - - impl Adder { - /// Creates a new `adder` from the given `accumulator`. - #[ink(constructor, payable)] - pub fn new(accumulator: AccumulatorRef) -> Self { - Self { accumulator } - } - - /// Increases the `accumulator` value by some amount. - #[ink(message)] - pub fn inc(&mut self, by: i32) { - self.accumulator.inc(by) - } - } -} diff --git a/integration-tests/multi-contract-caller/build-all.sh b/integration-tests/multi-contract-caller/build-all.sh deleted file mode 100755 index c6de737b5f9..00000000000 --- a/integration-tests/multi-contract-caller/build-all.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -cargo contract build --manifest-path accumulator/Cargo.toml -cargo contract build --manifest-path adder/Cargo.toml -cargo contract build --manifest-path subber/Cargo.toml -cargo contract build diff --git a/integration-tests/multi-contract-caller/lib.rs b/integration-tests/multi-contract-caller/lib.rs deleted file mode 100644 index 1e57271f81b..00000000000 --- a/integration-tests/multi-contract-caller/lib.rs +++ /dev/null @@ -1,212 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod multi_contract_caller { - use accumulator::AccumulatorRef; - use adder::AdderRef; - use subber::SubberRef; - - /// Specifies the state of the `multi_contract_caller` contract. - /// - /// In `Adder` state the `multi_contract_caller` contract will call the `Adder` - /// contract and in `Subber` state will call to the `Subber` contract. - /// - /// The initial state is `Adder`. - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Which { - Adder, - Subber, - } - - /// Calls to an `adder` or `subber` contract to mutate a value in an `accumulator` - /// contract. - /// - /// # Note - /// - /// In order to instantiate the `multi_contract_caller` smart contract we first - /// have to manually put the code of the `accumulator`, `adder` - /// and `subber` smart contracts, receive their code hashes from - /// the signalled events and put their code hash into our - /// `multi_contract_caller` smart contract. - /// - /// The `AccumulatorRef`, `AdderRef` and `SubberRef` are smart contract - /// reference types that have been automatically generated by ink!. - #[ink(storage)] - pub struct MultiContractCaller { - /// Says which of `adder` or `subber` is currently in use. - which: Which, - /// The `accumulator` smart contract. - accumulator: AccumulatorRef, - /// The `adder` smart contract. - adder: AdderRef, - /// The `subber` smart contract. - subber: SubberRef, - } - - impl MultiContractCaller { - /// Instantiate a `multi_contract_caller` contract with the given sub-contract - /// codes. - #[ink(constructor, payable)] - pub fn new( - init_value: i32, - version: u32, - accumulator_code_hash: Hash, - adder_code_hash: Hash, - subber_code_hash: Hash, - ) -> Self { - let total_balance = Self::env().balance(); - let salt = version.to_le_bytes(); - let accumulator = AccumulatorRef::new(init_value) - .endowment(total_balance / 4) - .code_hash(accumulator_code_hash) - .salt_bytes(salt) - .instantiate(); - let adder = AdderRef::new(accumulator.clone()) - .endowment(total_balance / 4) - .code_hash(adder_code_hash) - .salt_bytes(salt) - .instantiate(); - let subber = SubberRef::new(accumulator.clone()) - .endowment(total_balance / 4) - .code_hash(subber_code_hash) - .salt_bytes(salt) - .instantiate(); - Self { - which: Which::Adder, - accumulator, - adder, - subber, - } - } - - /// Returns the `accumulator` value. - #[ink(message)] - pub fn get(&self) -> i32 { - self.accumulator.get() - } - - /// Delegates the call to either `Adder` or `Subber`. - #[ink(message)] - pub fn change(&mut self, by: i32) { - match self.which { - Which::Adder => self.adder.inc(by), - Which::Subber => self.subber.dec(by), - } - } - - /// Switches the `multi_contract_caller` contract. - #[ink(message)] - pub fn switch(&mut self) { - match self.which { - Which::Adder => { - self.which = Which::Subber; - } - Which::Subber => { - self.which = Which::Adder; - } - } - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_multi_contract_caller( - mut client: Client, - ) -> E2EResult<()> { - // given - let accumulator_hash = client - .upload("accumulator", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `accumulator` failed") - .code_hash; - - let adder_hash = client - .upload("adder", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `adder` failed") - .code_hash; - - let subber_hash = client - .upload("subber", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `subber` failed") - .code_hash; - - let mut constructor = MultiContractCallerRef::new( - 1234, // initial value - 1337, // salt - accumulator_hash, - adder_hash, - subber_hash, - ); - - let multi_contract_caller = client - .instantiate("multi_contract_caller", &ink_e2e::alice(), &mut constructor) - .value(10_000_000_000_000) - .submit() - .await - .expect("instantiate failed"); - let mut call = multi_contract_caller.call::(); - - // when - let get = call.get(); - let value = client - .call(&ink_e2e::bob(), &get) - .dry_run() - .await? - .return_value(); - assert_eq!(value, 1234); - let change = call.change(6); - let _ = client - .call(&ink_e2e::bob(), &change) - .submit() - .await - .expect("calling `change` failed"); - - // then - let get = call.get(); - let value = client - .call(&ink_e2e::bob(), &get) - .dry_run() - .await? - .return_value(); - assert_eq!(value, 1234 + 6); - - // when - let switch = call.switch(); - let _ = client - .call(&ink_e2e::bob(), &switch) - .submit() - .await - .expect("calling `switch` failed"); - let change = call.change(3); - let _ = client - .call(&ink_e2e::bob(), &change) - .submit() - .await - .expect("calling `change` failed"); - - // then - let get = call.get(); - let value = client - .call(&ink_e2e::bob(), &get) - .dry_run() - .await? - .return_value(); - assert_eq!(value, 1234 + 6 - 3); - - Ok(()) - } - } -} diff --git a/integration-tests/multi-contract-caller/subber/Cargo.toml b/integration-tests/multi-contract-caller/subber/Cargo.toml deleted file mode 100644 index 8407a9e4cc4..00000000000 --- a/integration-tests/multi-contract-caller/subber/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "subber" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -accumulator = { path = "../accumulator", default-features = false, features = ["ink-as-dependency"] } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - - "accumulator/std", -] -ink-as-dependency = [] diff --git a/integration-tests/multi-contract-caller/subber/lib.rs b/integration-tests/multi-contract-caller/subber/lib.rs deleted file mode 100644 index 7b6e522ae0c..00000000000 --- a/integration-tests/multi-contract-caller/subber/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::subber::{ - Subber, - SubberRef, -}; - -#[ink::contract] -mod subber { - use accumulator::AccumulatorRef; - - /// Decreases the underlying `accumulator` value. - #[ink(storage)] - pub struct Subber { - /// The `accumulator` to store the value. - accumulator: AccumulatorRef, - } - - impl Subber { - /// Creates a new `subber` from the given `accumulator`. - #[ink(constructor, payable)] - pub fn new(accumulator: AccumulatorRef) -> Self { - Self { accumulator } - } - - /// Decreases the `accumulator` value by some amount. - #[ink(message)] - pub fn dec(&mut self, by: i32) { - self.accumulator.inc(0i32.checked_sub(by).unwrap()) - } - } -} diff --git a/integration-tests/multisig/.gitignore b/integration-tests/multisig/.gitignore deleted file mode 100755 index bf910de10af..00000000000 --- a/integration-tests/multisig/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/multisig/Cargo.toml b/integration-tests/multisig/Cargo.toml deleted file mode 100755 index d95ec880381..00000000000 --- a/integration-tests/multisig/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "multisig" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/multisig/lib.rs b/integration-tests/multisig/lib.rs deleted file mode 100755 index 623919891e5..00000000000 --- a/integration-tests/multisig/lib.rs +++ /dev/null @@ -1,1112 +0,0 @@ -//! # Multisig Wallet -//! -//! This implements a plain multi owner wallet. -//! -//! ## Warning -//! -//! This contract is an *example*. It is neither audited nor endorsed for production use. -//! Do **not** rely on it to keep anything of value secure. -//! -//! ## Overview -//! -//! Each instantiation of this contract has a set of `owners` and a `requirement` of -//! how many of them need to agree on a `Transaction` for it to be able to be executed. -//! Every owner can submit a transaction and when enough of the other owners confirm -//! it will be able to be executed. The following invariant is enforced by the contract: -//! -//! ```ignore -//! 0 < requirement && requirement <= owners && owners <= MAX_OWNERS -//! ``` -//! -//! ## Error Handling -//! -//! With the exception of `execute_transaction` no error conditions are signalled -//! through return types. Any error or invariant violation triggers a panic and therefore -//! rolls back the transaction. -//! -//! ## Interface -//! -//! The interface is modelled after the popular Gnosis multisig wallet. However, there -//! are subtle variations from the interface. For example the `confirm_transaction` -//! will never trigger the execution of a `Transaction` even if the threshold is reached. -//! A call of `execute_transaction` is always required. This can be called by anyone. -//! -//! All the messages that are declared as only callable by the wallet must go through -//! the usual submit, confirm, execute cycle as any other transaction that should be -//! called by the wallet. For example, to add an owner you would submit a transaction -//! that calls the wallets own `add_owner` message through `submit_transaction`. -//! -//! ### Owner Management -//! -//! The messages `add_owner`, `remove_owner`, and `replace_owner` can be used to manage -//! the owner set after instantiation. -//! -//! ### Changing the Requirement -//! -//! `change_requirement` can be used to tighten or relax the `requirement` of how many -//! owner signatures are needed to execute a `Transaction`. -//! -//! ### Transaction Management -//! -//! `submit_transaction`, `cancel_transaction`, `confirm_transaction`, -//! `revoke_confirmation` and `execute_transaction` are the bread and butter messages -//! of this contract. Use them to dispatch arbitrary messages to other contracts -//! with the wallet as a sender. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -pub use self::multisig::{ - ConfirmationStatus, - Multisig, - Transaction, -}; - -#[ink::contract] -mod multisig { - use ink::{ - env::{ - call::{ - build_call, - ExecutionInput, - }, - CallFlags, - }, - prelude::vec::Vec, - scale::Output, - storage::Mapping, - }; - - /// Tune this to your liking but be wary that allowing too many owners will not - /// perform well. - const MAX_OWNERS: u32 = 50; - - type TransactionId = u32; - const WRONG_TRANSACTION_ID: &str = - "The user specified an invalid transaction id. Abort."; - - /// A wrapper that allows us to encode a blob of bytes. - /// - /// We use this to pass the set of untyped (bytes) parameters to the `CallBuilder`. - #[derive(Clone)] - struct CallInput<'a>(&'a [u8]); - - impl<'a> ink::scale::Encode for CallInput<'a> { - fn encode_to(&self, dest: &mut T) { - dest.write(self.0); - } - } - - /// Indicates whether a transaction is already confirmed or needs further - /// confirmations. - #[derive(Clone, Copy)] - #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum ConfirmationStatus { - /// The transaction is already confirmed. - Confirmed, - /// Indicates how many confirmations are remaining. - ConfirmationsNeeded(u32), - } - - /// A Transaction is what every `owner` can submit for confirmation by other owners. - /// If enough owners agree it will be executed by the contract. - #[derive(Clone)] - #[cfg_attr( - feature = "std", - derive(Debug, PartialEq, Eq, ink::storage::traits::StorageLayout) - )] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct Transaction { - /// The `AccountId` of the contract that is called in this transaction. - pub callee: AccountId, - /// The selector bytes that identifies the function of the callee that should be - /// called. - pub selector: [u8; 4], - /// The SCALE encoded parameters that are passed to the called function. - pub input: Vec, - /// The amount of chain balance that is transferred to the callee. - pub transferred_value: Balance, - /// Gas limit for the execution of the call. - pub gas_limit: u64, - /// If set to true the transaction will be allowed to re-enter the multisig - /// contract. Re-entrancy can lead to vulnerabilities. Use at your own - /// risk. - pub allow_reentry: bool, - } - - /// Errors that can occur upon calling this contract. - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Error { - /// Returned if the call failed. - TransactionFailed, - } - - /// This is a book keeping struct that stores a list of all transaction ids and - /// also the next id to use. We need it for cleaning up the storage. - #[derive(Clone, Default)] - #[cfg_attr( - feature = "std", - derive(Debug, PartialEq, Eq, ink::storage::traits::StorageLayout) - )] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub struct Transactions { - /// Just store all transaction ids packed. - transactions: Vec, - /// We just increment this whenever a new transaction is created. - /// We never decrement or defragment. For now, the contract becomes defunct - /// when the ids are exhausted. - next_id: TransactionId, - } - - /// Emitted when an owner confirms a transaction. - #[ink(event)] - pub struct Confirmation { - /// The transaction that was confirmed. - #[ink(topic)] - transaction: TransactionId, - /// The owner that sent the confirmation. - #[ink(topic)] - from: AccountId, - /// The confirmation status after this confirmation was applied. - #[ink(topic)] - status: ConfirmationStatus, - } - - /// Emitted when an owner revoked a confirmation. - #[ink(event)] - pub struct Revocation { - /// The transaction that was revoked. - #[ink(topic)] - transaction: TransactionId, - /// The owner that sent the revocation. - #[ink(topic)] - from: AccountId, - } - - /// Emitted when an owner submits a transaction. - #[ink(event)] - pub struct Submission { - /// The transaction that was submitted. - #[ink(topic)] - transaction: TransactionId, - } - - /// Emitted when a transaction was canceled. - #[ink(event)] - pub struct Cancellation { - /// The transaction that was canceled. - #[ink(topic)] - transaction: TransactionId, - } - - /// Emitted when a transaction was executed. - #[ink(event)] - pub struct Execution { - /// The transaction that was executed. - #[ink(topic)] - transaction: TransactionId, - /// Indicates whether the transaction executed successfully. If so the `Ok` value - /// holds the output in bytes. The Option is `None` when the transaction - /// was executed through `invoke_transaction` rather than - /// `evaluate_transaction`. - #[ink(topic)] - result: Result>, Error>, - } - - /// Emitted when an owner is added to the wallet. - #[ink(event)] - pub struct OwnerAddition { - /// The owner that was added. - #[ink(topic)] - owner: AccountId, - } - - /// Emitted when an owner is removed from the wallet. - #[ink(event)] - pub struct OwnerRemoval { - /// The owner that was removed. - #[ink(topic)] - owner: AccountId, - } - - /// Emitted when the requirement changed. - #[ink(event)] - pub struct RequirementChange { - /// The new requirement value. - new_requirement: u32, - } - - #[ink(storage)] - #[derive(Default)] - pub struct Multisig { - /// Every entry in this map represents the confirmation of an owner for a - /// transaction. This is effectively a set rather than a map. - confirmations: Mapping<(TransactionId, AccountId), ()>, - /// The amount of confirmations for every transaction. This is a redundant - /// information and is kept in order to prevent iterating through the - /// confirmation set to check if a transaction is confirmed. - confirmation_count: Mapping, - /// Map the transaction id to its not-executed transaction. - transactions: Mapping, - /// We need to hold a list of all transactions so that we can clean up storage - /// when an owner is removed. - transaction_list: Transactions, - /// The list is a vector because iterating over it is necessary when cleaning - /// up the confirmation set. - owners: Vec, - /// Redundant information to speed up the check whether a caller is an owner. - is_owner: Mapping, - /// Minimum number of owners that have to confirm a transaction to be executed. - requirement: u32, - } - - impl Multisig { - /// The only constructor of the contract. - /// - /// A list of owners must be supplied and a number of how many of them must - /// confirm a transaction. Duplicate owners are silently dropped. - /// - /// # Panics - /// - /// If `requirement` violates our invariant. - #[ink(constructor)] - pub fn new(requirement: u32, mut owners: Vec) -> Self { - let mut contract = Multisig::default(); - owners.sort_unstable(); - owners.dedup(); - ensure_requirement_is_valid(owners.len() as u32, requirement); - - for owner in &owners { - contract.is_owner.insert(owner, &()); - } - - contract.owners = owners; - contract.transaction_list = Default::default(); - contract.requirement = requirement; - contract - } - - /// Add a new owner to the contract. - /// - /// Only callable by the wallet itself. - /// - /// # Panics - /// - /// If the owner already exists. - /// - /// # Examples - /// - /// Since this message must be send by the wallet itself it has to be build as a - /// `Transaction` and dispatched through `submit_transaction` and - /// `invoke_transaction`: - /// ```should_panic - /// use ink::{ - /// env::{ - /// call::{ - /// utils::ArgumentList, - /// Call, - /// CallParams, - /// ExecutionInput, - /// Selector, - /// }, - /// DefaultEnvironment as Env, - /// Environment, - /// }, - /// scale::Encode, - /// selector_bytes, - /// }; - /// use multisig::{ - /// ConfirmationStatus, - /// Transaction, - /// }; - /// - /// type AccountId = ::AccountId; - /// - /// // address of an existing `Multisig` contract - /// let wallet_id: AccountId = [7u8; 32].into(); - /// - /// // first create the transaction that adds `alice` through `add_owner` - /// let alice: AccountId = [1u8; 32].into(); - /// let add_owner_args = ArgumentList::empty().push_arg(&alice); - /// - /// let transaction_candidate = Transaction { - /// callee: wallet_id, - /// selector: selector_bytes!("add_owner"), - /// input: add_owner_args.encode(), - /// transferred_value: 0, - /// gas_limit: 0, - /// allow_reentry: true, - /// }; - /// - /// // Submit the transaction for confirmation - /// // - /// // Note that the selector bytes of the `submit_transaction` method - /// // are `[86, 244, 13, 223]`. - /// let (id, _status) = ink::env::call::build_call::() - /// .call_type(Call::new(wallet_id)) - /// .gas_limit(0) - /// .exec_input( - /// ExecutionInput::new(Selector::new([86, 244, 13, 223])) - /// .push_arg(&transaction_candidate), - /// ) - /// .returns::<(u32, ConfirmationStatus)>() - /// .invoke(); - /// - /// // Wait until all owners have confirmed and then execute the tx. - /// // - /// // Note that the selector bytes of the `invoke_transaction` method - /// // are `[185, 50, 225, 236]`. - /// ink::env::call::build_call::() - /// .call_type(Call::new(wallet_id)) - /// .gas_limit(0) - /// .exec_input(ExecutionInput::new(Selector::new([185, 50, 225, 236])).push_arg(&id)) - /// .returns::<()>() - /// .invoke(); - /// ``` - #[ink(message)] - pub fn add_owner(&mut self, new_owner: AccountId) { - self.ensure_from_wallet(); - self.ensure_no_owner(&new_owner); - ensure_requirement_is_valid( - (self.owners.len() as u32).checked_add(1).unwrap(), - self.requirement, - ); - self.is_owner.insert(new_owner, &()); - self.owners.push(new_owner); - self.env().emit_event(OwnerAddition { owner: new_owner }); - } - - /// Remove an owner from the contract. - /// - /// Only callable by the wallet itself. If by doing this the amount of owners - /// would be smaller than the requirement it is adjusted to be exactly the - /// number of owners. - /// - /// # Panics - /// - /// If `owner` is no owner of the wallet. - #[ink(message)] - pub fn remove_owner(&mut self, owner: AccountId) { - self.ensure_from_wallet(); - self.ensure_owner(&owner); - // If caller is an owner the len has to be > 0 - #[allow(clippy::arithmetic_side_effects)] - let len = self.owners.len() as u32 - 1; - let requirement = u32::min(len, self.requirement); - ensure_requirement_is_valid(len, requirement); - let owner_index = self.owner_index(&owner) as usize; - self.owners.swap_remove(owner_index); - self.is_owner.remove(owner); - self.requirement = requirement; - self.clean_owner_confirmations(&owner); - self.env().emit_event(OwnerRemoval { owner }); - } - - /// Replace an owner from the contract with a new one. - /// - /// Only callable by the wallet itself. - /// - /// # Panics - /// - /// If `old_owner` is no owner or if `new_owner` already is one. - #[ink(message)] - pub fn replace_owner(&mut self, old_owner: AccountId, new_owner: AccountId) { - self.ensure_from_wallet(); - self.ensure_owner(&old_owner); - self.ensure_no_owner(&new_owner); - let owner_index = self.owner_index(&old_owner); - self.owners[owner_index as usize] = new_owner; - self.is_owner.remove(old_owner); - self.is_owner.insert(new_owner, &()); - self.clean_owner_confirmations(&old_owner); - self.env().emit_event(OwnerRemoval { owner: old_owner }); - self.env().emit_event(OwnerAddition { owner: new_owner }); - } - - /// Change the requirement to a new value. - /// - /// Only callable by the wallet itself. - /// - /// # Panics - /// - /// If the `new_requirement` violates our invariant. - #[ink(message)] - pub fn change_requirement(&mut self, new_requirement: u32) { - self.ensure_from_wallet(); - ensure_requirement_is_valid(self.owners.len() as u32, new_requirement); - self.requirement = new_requirement; - self.env().emit_event(RequirementChange { new_requirement }); - } - - /// Add a new transaction candidate to the contract. - /// - /// This also confirms the transaction for the caller. This can be called by any - /// owner. - #[ink(message)] - pub fn submit_transaction( - &mut self, - transaction: Transaction, - ) -> (TransactionId, ConfirmationStatus) { - self.ensure_caller_is_owner(); - let trans_id = self.transaction_list.next_id; - self.transaction_list.next_id = - trans_id.checked_add(1).expect("Transaction ids exhausted."); - self.transactions.insert(trans_id, &transaction); - self.transaction_list.transactions.push(trans_id); - self.env().emit_event(Submission { - transaction: trans_id, - }); - ( - trans_id, - self.confirm_by_caller(self.env().caller(), trans_id), - ) - } - - /// Remove a transaction from the contract. - /// Only callable by the wallet itself. - /// - /// # Panics - /// - /// If `trans_id` is no valid transaction id. - #[ink(message)] - pub fn cancel_transaction(&mut self, trans_id: TransactionId) { - self.ensure_from_wallet(); - if self.take_transaction(trans_id).is_some() { - self.env().emit_event(Cancellation { - transaction: trans_id, - }); - } - } - - /// Confirm a transaction for the sender that was submitted by any owner. - /// - /// This can be called by any owner. - /// - /// # Panics - /// - /// If `trans_id` is no valid transaction id. - #[ink(message)] - pub fn confirm_transaction( - &mut self, - trans_id: TransactionId, - ) -> ConfirmationStatus { - self.ensure_caller_is_owner(); - self.ensure_transaction_exists(trans_id); - self.confirm_by_caller(self.env().caller(), trans_id) - } - - /// Revoke the senders confirmation. - /// - /// This can be called by any owner. - /// - /// # Panics - /// - /// If `trans_id` is no valid transaction id. - #[ink(message)] - pub fn revoke_confirmation(&mut self, trans_id: TransactionId) { - self.ensure_caller_is_owner(); - let caller = self.env().caller(); - if self.confirmations.contains((trans_id, caller)) { - self.confirmations.remove((trans_id, caller)); - let mut confirmation_count = self - .confirmation_count - .get(trans_id) - .expect( - "There is a entry in `self.confirmations`. Hence a count must exit.", - ); - // Will not underflow as there is at least one confirmation - #[allow(clippy::arithmetic_side_effects)] - { - confirmation_count -= 1; - } - self.confirmation_count - .insert(trans_id, &confirmation_count); - self.env().emit_event(Revocation { - transaction: trans_id, - from: caller, - }); - } - } - - /// Invoke a confirmed execution without getting its output. - /// - /// If the transaction which is invoked transfers value, this value has - /// to be sent as payment with this call. The method will fail otherwise, - /// and the transaction would then be reverted. - /// - /// Its return value indicates whether the called transaction was successful. - /// This can be called by anyone. - #[ink(message, payable)] - pub fn invoke_transaction( - &mut self, - trans_id: TransactionId, - ) -> Result<(), Error> { - self.ensure_confirmed(trans_id); - let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); - assert!(self.env().transferred_value() == t.transferred_value); - let call_flags = if t.allow_reentry { - CallFlags::ALLOW_REENTRY - } else { - CallFlags::empty() - }; - - let result = build_call::<::Env>() - .call(t.callee) - .gas_limit(t.gas_limit) - .transferred_value(t.transferred_value) - .call_flags(call_flags) - .exec_input( - ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), - ) - .returns::<()>() - .try_invoke(); - - let result = match result { - Ok(Ok(_)) => Ok(()), - _ => Err(Error::TransactionFailed), - }; - - self.env().emit_event(Execution { - transaction: trans_id, - result: result.map(|_| None), - }); - result - } - - /// Evaluate a confirmed execution and return its output as bytes. - /// - /// Its return value indicates whether the called transaction was successful and - /// contains its output when successful. - /// This can be called by anyone. - #[ink(message, payable)] - pub fn eval_transaction( - &mut self, - trans_id: TransactionId, - ) -> Result, Error> { - self.ensure_confirmed(trans_id); - let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); - let call_flags = if t.allow_reentry { - CallFlags::ALLOW_REENTRY - } else { - CallFlags::empty() - }; - - let result = build_call::<::Env>() - .call(t.callee) - .gas_limit(t.gas_limit) - .transferred_value(t.transferred_value) - .call_flags(call_flags) - .exec_input( - ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), - ) - .returns::>() - .try_invoke(); - - let result = match result { - Ok(Ok(v)) => Ok(v), - _ => Err(Error::TransactionFailed), - }; - - self.env().emit_event(Execution { - transaction: trans_id, - result: result.clone().map(Some), - }); - result - } - - /// Set the `transaction` as confirmed by `confirmer`. - /// Idempotent operation regarding an already confirmed `transaction` - /// by `confirmer`. - fn confirm_by_caller( - &mut self, - confirmer: AccountId, - transaction: TransactionId, - ) -> ConfirmationStatus { - let mut count = self.confirmation_count.get(transaction).unwrap_or(0); - let key = (transaction, confirmer); - let new_confirmation = !self.confirmations.contains(key); - if new_confirmation { - count = count.checked_add(1).unwrap(); - self.confirmations.insert(key, &()); - self.confirmation_count.insert(transaction, &count); - } - let status = { - if count >= self.requirement { - ConfirmationStatus::Confirmed - } else { - // We checked that count < self.requirement - #[allow(clippy::arithmetic_side_effects)] - ConfirmationStatus::ConfirmationsNeeded(self.requirement - count) - } - }; - if new_confirmation { - self.env().emit_event(Confirmation { - transaction, - from: confirmer, - status, - }); - } - status - } - - /// Get the index of `owner` in `self.owners`. - /// Panics if `owner` is not found in `self.owners`. - fn owner_index(&self, owner: &AccountId) -> u32 { - self.owners.iter().position(|x| *x == *owner).expect( - "This is only called after it was already verified that the id is - actually an owner.", - ) as u32 - } - - /// Remove the transaction identified by `trans_id` from `self.transactions`. - /// Also removes all confirmation state associated with it. - fn take_transaction(&mut self, trans_id: TransactionId) -> Option { - let transaction = self.transactions.get(trans_id); - if transaction.is_some() { - self.transactions.remove(trans_id); - let pos = self - .transaction_list - .transactions - .iter() - .position(|t| t == &trans_id) - .expect("The transaction exists hence it must also be in the list."); - self.transaction_list.transactions.swap_remove(pos); - for owner in self.owners.iter() { - self.confirmations.remove((trans_id, *owner)); - } - self.confirmation_count.remove(trans_id); - } - transaction - } - - /// Remove all confirmation state associated with `owner`. - /// Also adjusts the `self.confirmation_count` variable. - fn clean_owner_confirmations(&mut self, owner: &AccountId) { - for trans_id in &self.transaction_list.transactions { - let key = (*trans_id, *owner); - if self.confirmations.contains(key) { - self.confirmations.remove(key); - let mut count = self.confirmation_count.get(trans_id).unwrap_or(0); - count = count.saturating_sub(1); - self.confirmation_count.insert(trans_id, &count); - } - } - } - - /// Panic if transaction `trans_id` is not confirmed by at least - /// `self.requirement` owners. - fn ensure_confirmed(&self, trans_id: TransactionId) { - assert!( - self.confirmation_count - .get(trans_id) - .expect(WRONG_TRANSACTION_ID) - >= self.requirement - ); - } - - /// Panic if the transaction `trans_id` does not exit. - fn ensure_transaction_exists(&self, trans_id: TransactionId) { - self.transactions.get(trans_id).expect(WRONG_TRANSACTION_ID); - } - - /// Panic if the sender is no owner of the wallet. - fn ensure_caller_is_owner(&self) { - self.ensure_owner(&self.env().caller()); - } - - /// Panic if the sender is not this wallet. - fn ensure_from_wallet(&self) { - assert_eq!(self.env().caller(), self.env().account_id()); - } - - /// Panic if `owner` is not an owner, - fn ensure_owner(&self, owner: &AccountId) { - assert!(self.is_owner.contains(owner)); - } - - /// Panic if `owner` is an owner. - fn ensure_no_owner(&self, owner: &AccountId) { - assert!(!self.is_owner.contains(owner)); - } - } - - /// Panic if the number of `owners` under a `requirement` violates our - /// requirement invariant. - fn ensure_requirement_is_valid(owners: u32, requirement: u32) { - assert!(0 < requirement && requirement <= owners && owners <= MAX_OWNERS); - } - - #[cfg(test)] - mod tests { - use super::*; - use ink::env::{ - call::utils::ArgumentList, - test, - }; - - const WALLET: [u8; 32] = [7; 32]; - - impl Transaction { - fn change_requirement(requirement: u32) -> Self { - use ink::scale::Encode; - let call_args = ArgumentList::empty().push_arg(&requirement); - - // Multisig::change_requirement() - Self { - callee: AccountId::from(WALLET), - selector: ink::selector_bytes!("change_requirement"), - input: call_args.encode(), - transferred_value: 0, - gas_limit: 1000000, - allow_reentry: false, - } - } - } - - fn set_caller(sender: AccountId) { - ink::env::test::set_caller::(sender); - } - - fn set_from_wallet() { - let callee = AccountId::from(WALLET); - set_caller(callee); - } - - fn set_from_owner() { - let accounts = default_accounts(); - set_caller(accounts.alice); - } - - fn set_from_no_owner() { - let accounts = default_accounts(); - set_caller(accounts.django); - } - - fn default_accounts() -> test::DefaultAccounts { - ink::env::test::default_accounts::() - } - - fn build_contract() -> Multisig { - // Set the contract's address as `WALLET`. - let callee: AccountId = AccountId::from(WALLET); - ink::env::test::set_callee::(callee); - - let accounts = default_accounts(); - let owners = vec![accounts.alice, accounts.bob, accounts.eve]; - Multisig::new(2, owners) - } - - fn submit_transaction() -> Multisig { - let mut contract = build_contract(); - let accounts = default_accounts(); - set_from_owner(); - contract.submit_transaction(Transaction::change_requirement(1)); - assert_eq!(contract.transaction_list.transactions.len(), 1); - assert_eq!(test::recorded_events().count(), 2); - let transaction = contract.transactions.get(0).unwrap(); - assert_eq!(transaction, Transaction::change_requirement(1)); - contract.confirmations.get((0, accounts.alice)).unwrap(); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); - contract - } - - #[ink::test] - fn construction_works() { - let accounts = default_accounts(); - let owners = [accounts.alice, accounts.bob, accounts.eve]; - let contract = build_contract(); - - assert_eq!(contract.owners.len(), 3); - assert_eq!(contract.requirement, 2); - assert!(contract.owners.iter().eq(owners.iter())); - assert!(contract.is_owner.contains(accounts.alice)); - assert!(contract.is_owner.contains(accounts.bob)); - assert!(contract.is_owner.contains(accounts.eve)); - assert!(!contract.is_owner.contains(accounts.charlie)); - assert!(!contract.is_owner.contains(accounts.django)); - assert!(!contract.is_owner.contains(accounts.frank)); - assert_eq!(contract.transaction_list.transactions.len(), 0); - } - - #[ink::test] - #[should_panic] - fn empty_owner_construction_fails() { - Multisig::new(0, vec![]); - } - - #[ink::test] - #[should_panic] - fn zero_requirement_construction_fails() { - let accounts = default_accounts(); - Multisig::new(0, vec![accounts.alice, accounts.bob]); - } - - #[ink::test] - #[should_panic] - fn too_large_requirement_construction_fails() { - let accounts = default_accounts(); - Multisig::new(3, vec![accounts.alice, accounts.bob]); - } - - #[ink::test] - fn add_owner_works() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - let owners = contract.owners.len(); - contract.add_owner(accounts.frank); - assert_eq!(contract.owners.len(), owners + 1); - assert!(contract.is_owner.contains(accounts.frank)); - assert_eq!(test::recorded_events().count(), 1); - } - - #[ink::test] - #[should_panic] - fn add_existing_owner_fails() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - contract.add_owner(accounts.bob); - } - - #[ink::test] - #[should_panic] - fn add_owner_permission_denied() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_owner(); - contract.add_owner(accounts.frank); - } - - #[ink::test] - fn remove_owner_works() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - let owners = contract.owners.len(); - contract.remove_owner(accounts.alice); - assert_eq!(contract.owners.len(), owners - 1); - assert!(!contract.is_owner.contains(accounts.alice)); - assert_eq!(test::recorded_events().count(), 1); - } - - #[ink::test] - #[should_panic] - fn remove_owner_nonexisting_fails() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - contract.remove_owner(accounts.django); - } - - #[ink::test] - #[should_panic] - fn remove_owner_permission_denied() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_owner(); - contract.remove_owner(accounts.alice); - } - - #[ink::test] - fn replace_owner_works() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - let owners = contract.owners.len(); - contract.replace_owner(accounts.alice, accounts.django); - assert_eq!(contract.owners.len(), owners); - assert!(!contract.is_owner.contains(accounts.alice)); - assert!(contract.is_owner.contains(accounts.django)); - assert_eq!(test::recorded_events().count(), 2); - } - - #[ink::test] - #[should_panic] - fn replace_owner_existing_fails() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - contract.replace_owner(accounts.alice, accounts.bob); - } - - #[ink::test] - #[should_panic] - fn replace_owner_nonexisting_fails() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_wallet(); - contract.replace_owner(accounts.django, accounts.frank); - } - - #[ink::test] - #[should_panic] - fn replace_owner_permission_denied() { - let accounts = default_accounts(); - let mut contract = build_contract(); - set_from_owner(); - contract.replace_owner(accounts.alice, accounts.django); - } - - #[ink::test] - fn change_requirement_works() { - let mut contract = build_contract(); - assert_eq!(contract.requirement, 2); - set_from_wallet(); - contract.change_requirement(3); - assert_eq!(contract.requirement, 3); - assert_eq!(test::recorded_events().count(), 1); - } - - #[ink::test] - #[should_panic] - fn change_requirement_too_high() { - let mut contract = build_contract(); - set_from_wallet(); - contract.change_requirement(4); - } - - #[ink::test] - #[should_panic] - fn change_requirement_zero_fails() { - let mut contract = build_contract(); - set_from_wallet(); - contract.change_requirement(0); - } - - #[ink::test] - fn submit_transaction_works() { - submit_transaction(); - } - - #[ink::test] - #[should_panic] - fn submit_transaction_no_owner_fails() { - let mut contract = build_contract(); - set_from_no_owner(); - contract.submit_transaction(Transaction::change_requirement(1)); - } - - #[ink::test] - #[should_panic] - fn submit_transaction_wallet_fails() { - let mut contract = build_contract(); - set_from_wallet(); - contract.submit_transaction(Transaction::change_requirement(1)); - } - - #[ink::test] - fn cancel_transaction_works() { - let mut contract = submit_transaction(); - set_from_wallet(); - contract.cancel_transaction(0); - assert_eq!(contract.transaction_list.transactions.len(), 0); - assert_eq!(test::recorded_events().count(), 3); - } - - #[ink::test] - fn cancel_transaction_nonexisting() { - let mut contract = submit_transaction(); - set_from_wallet(); - contract.cancel_transaction(1); - assert_eq!(contract.transaction_list.transactions.len(), 1); - assert_eq!(test::recorded_events().count(), 2); - } - - #[ink::test] - #[should_panic] - fn cancel_transaction_no_permission() { - let mut contract = submit_transaction(); - contract.cancel_transaction(0); - } - - #[ink::test] - fn confirm_transaction_works() { - let mut contract = submit_transaction(); - let accounts = default_accounts(); - set_caller(accounts.bob); - contract.confirm_transaction(0); - assert_eq!(test::recorded_events().count(), 3); - contract.confirmations.get((0, accounts.bob)).unwrap(); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 2); - } - - #[ink::test] - fn revoke_confirmations() { - // given - let mut contract = submit_transaction(); - let accounts = default_accounts(); - // Confirm by Bob - set_caller(accounts.bob); - contract.confirm_transaction(0); - // Confirm by Eve - set_caller(accounts.eve); - contract.confirm_transaction(0); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 3); - // Revoke from Eve - contract.revoke_confirmation(0); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 2); - // Revoke from Bob - set_caller(accounts.bob); - contract.revoke_confirmation(0); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); - } - - #[ink::test] - fn confirm_transaction_already_confirmed() { - let mut contract = submit_transaction(); - let accounts = default_accounts(); - set_caller(accounts.alice); - contract.confirm_transaction(0); - assert_eq!(test::recorded_events().count(), 2); - contract.confirmations.get((0, accounts.alice)).unwrap(); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); - } - - #[ink::test] - #[should_panic] - fn confirm_transaction_no_owner_fail() { - let mut contract = submit_transaction(); - set_from_no_owner(); - contract.confirm_transaction(0); - } - - #[ink::test] - fn revoke_transaction_works() { - let mut contract = submit_transaction(); - let accounts = default_accounts(); - set_caller(accounts.alice); - contract.revoke_confirmation(0); - assert_eq!(test::recorded_events().count(), 3); - assert!(!contract.confirmations.contains((0, accounts.alice))); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 0); - } - - #[ink::test] - fn revoke_transaction_no_confirmer() { - let mut contract = submit_transaction(); - let accounts = default_accounts(); - set_caller(accounts.bob); - contract.revoke_confirmation(0); - assert_eq!(test::recorded_events().count(), 2); - assert!(contract.confirmations.contains((0, accounts.alice))); - assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); - } - - #[ink::test] - #[should_panic] - fn revoke_transaction_no_owner_fail() { - let mut contract = submit_transaction(); - let accounts = default_accounts(); - set_caller(accounts.django); - contract.revoke_confirmation(0); - } - - #[ink::test] - fn execute_transaction_works() { - // Execution of calls is currently unsupported in off-chain test. - // Calling `execute_transaction` panics in any case. - } - } -} diff --git a/integration-tests/payment-channel/.gitignore b/integration-tests/payment-channel/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/payment-channel/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/payment-channel/Cargo.toml b/integration-tests/payment-channel/Cargo.toml deleted file mode 100755 index 977befcfb25..00000000000 --- a/integration-tests/payment-channel/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "payment_channel" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -hex-literal = { version = "0.4.1" } -sp-core = { version = "21.0.0", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "sp-core/std", -] - -ink-as-dependency = [] diff --git a/integration-tests/payment-channel/lib.rs b/integration-tests/payment-channel/lib.rs deleted file mode 100755 index 877d039370a..00000000000 --- a/integration-tests/payment-channel/lib.rs +++ /dev/null @@ -1,590 +0,0 @@ -//! # Payment Channel -//! -//! This implements a payment channel between two parties. -//! -//! ## Warning -//! -//! This contract is an *example*. It is neither audited nor endorsed for production use. -//! Do **not** rely on it to keep anything of value secure. -//! -//! ## Overview -//! -//! Each instantiation of this contract creates a payment channel between a `sender` and a -//! `recipient`. It uses ECDSA signatures to ensure that the `recipient` can only claim -//! the funds if it is signed by the `sender`. -//! -//! ## Error Handling -//! -//! The only panic in the contract is when the signature is invalid. For all other -//! error cases an error is returned. Possible errors are defined in the `Error` enum. -//! -//! ## Interface -//! -//! The interface is modelled after [this blog post](https://programtheblockchain.com/posts/2018/03/02/building-long-lived-payment-channels) -//! -//! ### Deposits -//! -//! The creator of the contract, i.e the `sender`, can deposit funds to the payment -//! channel while creating the payment channel. Any subsequent deposits can be made by -//! transferring funds to the contract's address. -//! -//! ### Withdrawals -//! -//! The `recipient` can `withdraw` from the payment channel anytime by submitting the last -//! `signature` received from the `sender`. -//! -//! The `sender` can only `withdraw` by terminating the payment channel. This is -//! done by calling `start_sender_close` to set an expiration with a subsequent call -//! of `claim_timeout` to claim the funds. This will terminate the payment channel. - -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod payment_channel { - - /// Struct for storing the payment channel details. - /// The creator of the contract, i.e the `sender`, can deposit funds to the payment - /// channel while deploying the contract. - #[ink(storage)] - pub struct PaymentChannel { - /// The `AccountId` of the sender of the payment channel. - sender: AccountId, - /// The `AccountId` of the recipient of the payment channel. - recipient: AccountId, - /// The `Timestamp` at which the contract expires. The field is optional. - /// The contract never expires if set to `None`. - expiration: Option, - /// The `Amount` withdrawn by the recipient. - withdrawn: Balance, - /// The `Timestamp` which will be added to the current time when the sender - /// wishes to close the channel. This will be set at the time of contract - /// instantiation. - close_duration: Timestamp, - } - - /// Errors that can occur upon calling this contract. - #[derive(Debug, PartialEq, Eq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Error { - /// Returned if caller is not the `sender` while required to. - CallerIsNotSender, - /// Returned if caller is not the `recipient` while required to. - CallerIsNotRecipient, - /// Returned if the requested withdrawal amount is less than the amount - /// that is already already withdrawn. - AmountIsLessThanWithdrawn, - /// Returned if the requested transfer failed. This can be the case if the - /// contract does not have sufficient free funds or if the transfer would - /// have brought the contract's balance below minimum balance. - TransferFailed, - /// Returned if the contract hasn't expired yet and the `sender` wishes to - /// close the channel. - NotYetExpired, - /// Returned if the signature is invalid. - InvalidSignature, - } - - /// Type alias for the contract's `Result` type. - pub type Result = core::result::Result; - - /// Emitted when the sender starts closing the channel. - #[ink(event)] - pub struct SenderCloseStarted { - expiration: Timestamp, - close_duration: Timestamp, - } - - impl PaymentChannel { - /// The only constructor of the contract. - /// - /// The arguments `recipient` and `close_duration` are required. - /// - /// `expiration` will be set to `None`, so that the contract will - /// never expire. `sender` can call `start_sender_close` to override - /// this. `sender` will be able to claim the remaining balance by calling - /// `claim_timeout` after `expiration` has passed. - #[ink(constructor)] - pub fn new(recipient: AccountId, close_duration: Timestamp) -> Self { - Self { - sender: Self::env().caller(), - recipient, - expiration: None, - withdrawn: 0, - close_duration, - } - } - - /// The `recipient` can close the payment channel anytime. The specified - /// `amount` will be sent to the `recipient` and the remainder will go - /// back to the `sender`. - #[ink(message)] - pub fn close(&mut self, amount: Balance, signature: [u8; 65]) -> Result<()> { - self.close_inner(amount, signature)?; - self.env().terminate_contract(self.sender); - } - - /// We split this out in order to make testing `close` simpler. - fn close_inner(&mut self, amount: Balance, signature: [u8; 65]) -> Result<()> { - if self.env().caller() != self.recipient { - return Err(Error::CallerIsNotRecipient) - } - - if amount < self.withdrawn { - return Err(Error::AmountIsLessThanWithdrawn) - } - - // Signature validation - if !self.is_signature_valid(amount, signature) { - return Err(Error::InvalidSignature) - } - - // We checked that amount >= self.withdrawn - #[allow(clippy::arithmetic_side_effects)] - self.env() - .transfer(self.recipient, amount - self.withdrawn) - .map_err(|_| Error::TransferFailed)?; - - Ok(()) - } - - /// If the `sender` wishes to close the channel and withdraw the funds they can - /// do so by setting the `expiration`. If the `expiration` is reached, the - /// sender will be able to call `claim_timeout` to claim the remaining funds - /// and the channel will be terminated. This emits an event that the recipient can - /// listen to in order to withdraw the funds before the `expiration`. - #[ink(message)] - pub fn start_sender_close(&mut self) -> Result<()> { - if self.env().caller() != self.sender { - return Err(Error::CallerIsNotSender) - } - - let now = self.env().block_timestamp(); - let expiration = now.checked_add(self.close_duration).unwrap(); - - self.env().emit_event(SenderCloseStarted { - expiration, - close_duration: self.close_duration, - }); - - self.expiration = Some(expiration); - - Ok(()) - } - - /// If the timeout is reached (`current_time >= expiration`) without the - /// recipient closing the channel, then the remaining balance is released - /// back to the `sender`. - #[ink(message)] - pub fn claim_timeout(&mut self) -> Result<()> { - match self.expiration { - Some(expiration) => { - // expiration is set. Check if it's reached and if so, release the - // funds and terminate the contract. - let now = self.env().block_timestamp(); - if now < expiration { - return Err(Error::NotYetExpired) - } - - self.env().terminate_contract(self.sender); - } - - None => Err(Error::NotYetExpired), - } - } - - /// The `recipient` can withdraw the funds from the channel at any time. - #[ink(message)] - pub fn withdraw(&mut self, amount: Balance, signature: [u8; 65]) -> Result<()> { - if self.env().caller() != self.recipient { - return Err(Error::CallerIsNotRecipient) - } - - // Signature validation - if !self.is_signature_valid(amount, signature) { - return Err(Error::InvalidSignature) - } - - // Make sure there's something to withdraw (guards against underflow) - if amount < self.withdrawn { - return Err(Error::AmountIsLessThanWithdrawn) - } - - // We checked that amount >= self.withdrawn - #[allow(clippy::arithmetic_side_effects)] - let amount_to_withdraw = amount - self.withdrawn; - self.withdrawn.checked_add(amount_to_withdraw).unwrap(); - - self.env() - .transfer(self.recipient, amount_to_withdraw) - .map_err(|_| Error::TransferFailed)?; - - Ok(()) - } - - /// Returns the `sender` of the contract. - #[ink(message)] - pub fn get_sender(&self) -> AccountId { - self.sender - } - - /// Returns the `recipient` of the contract. - #[ink(message)] - pub fn get_recipient(&self) -> AccountId { - self.recipient - } - - /// Returns the `expiration` of the contract. - #[ink(message)] - pub fn get_expiration(&self) -> Option { - self.expiration - } - - /// Returns the `withdrawn` amount of the contract. - #[ink(message)] - pub fn get_withdrawn(&self) -> Balance { - self.withdrawn - } - - /// Returns the `close_duration` of the contract. - #[ink(message)] - pub fn get_close_duration(&self) -> Timestamp { - self.close_duration - } - - /// Returns the `balance` of the contract. - #[ink(message)] - pub fn get_balance(&self) -> Balance { - self.env().balance() - } - } - - #[ink(impl)] - impl PaymentChannel { - fn is_signature_valid(&self, amount: Balance, signature: [u8; 65]) -> bool { - let encodable = (self.env().account_id(), amount); - let mut message = - ::Type::default(); - ink::env::hash_encoded::( - &encodable, - &mut message, - ); - - let mut pub_key = [0; 33]; - ink::env::ecdsa_recover(&signature, &message, &mut pub_key) - .unwrap_or_else(|err| panic!("recover failed: {err:?}")); - let mut signature_account_id = [0; 32]; - ::hash( - &pub_key, - &mut signature_account_id, - ); - - self.recipient == signature_account_id.into() - } - } - - #[cfg(test)] - mod tests { - use super::*; - - use hex_literal; - use sp_core::{ - Encode, - Pair, - }; - - fn default_accounts( - ) -> ink::env::test::DefaultAccounts { - ink::env::test::default_accounts::() - } - - fn set_next_caller(caller: AccountId) { - ink::env::test::set_caller::(caller); - } - - fn set_account_balance(account: AccountId, balance: Balance) { - ink::env::test::set_account_balance::( - account, balance, - ); - } - - fn get_account_balance(account: AccountId) -> Balance { - ink::env::test::get_account_balance::(account) - .expect("Cannot get account balance") - } - - fn advance_block() { - ink::env::test::advance_block::(); - } - - fn get_current_time() -> Timestamp { - let since_the_epoch = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("Time went backwards"); - since_the_epoch.as_secs() - + since_the_epoch.subsec_nanos() as u64 / 1_000_000_000 - } - - fn get_dan() -> AccountId { - // Use Dan's seed - // `subkey inspect //Dan --scheme Ecdsa --output-type json | jq .secretSeed` - let seed = hex_literal::hex!( - "c31fa562972de437802e0df146b16146349590b444db41f7e3eb9deedeee6f64" - ); - let pair = sp_core::ecdsa::Pair::from_seed(&seed); - let pub_key = pair.public(); - let compressed_pub_key: [u8; 33] = pub_key.encode()[..] - .try_into() - .expect("slice with incorrect length"); - let mut account_id = [0; 32]; - ::hash( - &compressed_pub_key, - &mut account_id, - ); - account_id.into() - } - - fn contract_id() -> AccountId { - let accounts = default_accounts(); - let contract_id = accounts.charlie; - ink::env::test::set_callee::(contract_id); - contract_id - } - - fn sign(contract_id: AccountId, amount: Balance) -> [u8; 65] { - let encodable = (contract_id, amount); - let mut hash = - ::Type::default(); // 256-bit buffer - ink::env::hash_encoded::(&encodable, &mut hash); - - // Use Dan's seed - // `subkey inspect //Dan --scheme Ecdsa --output-type json | jq .secretSeed` - let seed = hex_literal::hex!( - "c31fa562972de437802e0df146b16146349590b444db41f7e3eb9deedeee6f64" - ); - let pair = sp_core::ecdsa::Pair::from_seed(&seed); - - let signature = pair.sign_prehashed(&hash); - signature.0 - } - - #[ink::test] - fn test_deposit() { - // given - let accounts = default_accounts(); - let initial_balance = 10_000; - let close_duration = 360_000; - let mock_deposit_value = 1_000; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(accounts.bob, initial_balance); - - // when - // Push the new execution context with Alice as the caller and - // the `mock_deposit_value` as the value deposited. - // Note: Currently there is no way to transfer funds to the contract. - set_next_caller(accounts.alice); - let payment_channel = PaymentChannel::new(accounts.bob, close_duration); - let contract_id = contract_id(); - set_account_balance(contract_id, mock_deposit_value); - - // then - assert_eq!(payment_channel.get_balance(), mock_deposit_value); - } - - #[ink::test] - fn test_close() { - // given - let accounts = default_accounts(); - let dan = get_dan(); - let close_duration = 360_000; - let mock_deposit_value = 1_000; - let amount = 500; - let initial_balance = 10_000; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(dan, initial_balance); - - // when - set_next_caller(accounts.alice); - let mut payment_channel = PaymentChannel::new(dan, close_duration); - let contract_id = contract_id(); - set_account_balance(contract_id, mock_deposit_value); - set_next_caller(dan); - let signature = sign(contract_id, amount); - - // then - let should_close = move || payment_channel.close(amount, signature).unwrap(); - ink::env::test::assert_contract_termination::( - should_close, - accounts.alice, - amount, - ); - assert_eq!(get_account_balance(dan), initial_balance + amount); - } - - #[ink::test] - fn close_fails_invalid_signature() { - // given - let accounts = default_accounts(); - let dan = get_dan(); - let mock_deposit_value = 1_000; - let close_duration = 360_000; - let amount = 400; - let unexpected_amount = amount + 1; - let initial_balance = 10_000; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(dan, initial_balance); - - // when - set_next_caller(accounts.alice); - let mut payment_channel = PaymentChannel::new(dan, close_duration); - let contract_id = contract_id(); - set_account_balance(contract_id, mock_deposit_value); - set_next_caller(dan); - let signature = sign(contract_id, amount); - - // then - let res = payment_channel.close_inner(unexpected_amount, signature); - assert!(res.is_err(), "Expected an error, got {res:?} instead."); - assert_eq!(res.unwrap_err(), Error::InvalidSignature,); - } - - #[ink::test] - fn test_withdraw() { - // given - let accounts = default_accounts(); - let dan = get_dan(); - let initial_balance = 10_000; - let mock_deposit_value = 1_000; - let close_duration = 360_000; - let amount = 500; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(dan, initial_balance); - - // when - set_next_caller(accounts.alice); - let mut payment_channel = PaymentChannel::new(dan, close_duration); - let contract_id = contract_id(); - set_account_balance(contract_id, mock_deposit_value); - - set_next_caller(dan); - let signature = sign(contract_id, amount); - payment_channel - .withdraw(amount, signature) - .expect("withdraw failed"); - - // then - assert_eq!(payment_channel.get_balance(), amount); - assert_eq!(get_account_balance(dan), initial_balance + amount); - } - - #[ink::test] - fn withdraw_fails_invalid_signature() { - // given - let accounts = default_accounts(); - let dan = get_dan(); - let initial_balance = 10_000; - let close_duration = 360_000; - let amount = 400; - let unexpected_amount = amount + 1; - let mock_deposit_value = 1_000; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(dan, initial_balance); - - // when - set_next_caller(accounts.alice); - let mut payment_channel = PaymentChannel::new(dan, close_duration); - let contract_id = contract_id(); - set_account_balance(contract_id, mock_deposit_value); - set_next_caller(dan); - let signature = sign(contract_id, amount); - - // then - let res = payment_channel.withdraw(unexpected_amount, signature); - assert!(res.is_err(), "Expected an error, got {res:?} instead."); - assert_eq!(res.unwrap_err(), Error::InvalidSignature,); - } - - #[ink::test] - fn test_start_sender_close() { - // given - let accounts = default_accounts(); - let initial_balance = 10_000; - let mock_deposit_value = 1_000; - let close_duration = 1; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(accounts.bob, initial_balance); - - // when - set_next_caller(accounts.alice); - let mut payment_channel = PaymentChannel::new(accounts.bob, close_duration); - let contract_id = contract_id(); - set_account_balance(contract_id, mock_deposit_value); - - payment_channel - .start_sender_close() - .expect("start_sender_close failed"); - advance_block(); - - // then - let now = get_current_time(); - assert!(now > payment_channel.get_expiration().unwrap()); - } - - #[ink::test] - fn test_claim_timeout() { - // given - let accounts = default_accounts(); - let initial_balance = 10_000; - let close_duration = 1; - let mock_deposit_value = 1_000; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(accounts.bob, initial_balance); - - // when - set_next_caller(accounts.alice); - let contract_id = contract_id(); - let mut payment_channel = PaymentChannel::new(accounts.bob, close_duration); - set_account_balance(contract_id, mock_deposit_value); - - payment_channel - .start_sender_close() - .expect("start_sender_close failed"); - advance_block(); - - // then - let should_close = move || payment_channel.claim_timeout().unwrap(); - ink::env::test::assert_contract_termination::( - should_close, - accounts.alice, - mock_deposit_value, - ); - assert_eq!( - get_account_balance(accounts.alice), - initial_balance + mock_deposit_value - ); - } - - #[ink::test] - fn test_getters() { - // given - let accounts = default_accounts(); - let initial_balance = 10_000; - let mock_deposit_value = 1_000; - let close_duration = 360_000; - set_account_balance(accounts.alice, initial_balance); - set_account_balance(accounts.bob, initial_balance); - - // when - set_next_caller(accounts.alice); - let contract_id = contract_id(); - let payment_channel = PaymentChannel::new(accounts.bob, close_duration); - set_account_balance(contract_id, mock_deposit_value); - - // then - assert_eq!(payment_channel.get_sender(), accounts.alice); - assert_eq!(payment_channel.get_recipient(), accounts.bob); - assert_eq!(payment_channel.get_balance(), mock_deposit_value); - assert_eq!(payment_channel.get_close_duration(), close_duration); - assert_eq!(payment_channel.get_withdrawn(), 0); - } - } -} diff --git a/integration-tests/psp22-extension/.gitignore b/integration-tests/psp22-extension/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/psp22-extension/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/psp22-extension/Cargo.toml b/integration-tests/psp22-extension/Cargo.toml deleted file mode 100755 index 58890b6cd79..00000000000 --- a/integration-tests/psp22-extension/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "psp22_extension" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/psp22-extension/README.md b/integration-tests/psp22-extension/README.md deleted file mode 100644 index 040662f5639..00000000000 --- a/integration-tests/psp22-extension/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# PSP22 Chain Extension Example - -## What is this example about? - -It is an example implementation of the -[PSP22 Fungible Token Standard](https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md) -as a chain extension, supporting a multi-token system provided by the -[FRAME assets pallet](https://docs.substrate.io/rustdocs/latest/pallet_assets/index.html). -It effectively allows ink! contracts (L2) to interact with native assets (L1) from the -chain runtime in a standardized way. - -See [this chapter](https://paritytech.github.io/ink-docs/macros-attributes/chain-extension) -in our ink! documentation for more details about chain extensions. - -There are two parts to this example: - -* Defining and calling the extension in ink!. -* Defining the extension in Substrate. - -## Chain-side Integration - -To integrate this example into Substrate you need to do two things: - -* In your runtime, use the code in - [`psp22-extension-example.rs`](runtime/psp22-extension-example.rs) - as an implementation for the trait `ChainExtension` in Substrate. - You can just copy/paste that file as a new module, e.g. `runtime/src/chain_extension.rs`. - -* In your runtime, use the implementation as the associated type `ChainExtension` of the - trait `pallet_contracts::Config`: - ```rust - impl pallet_contracts::Config for Runtime { - … - type ChainExtension = Psp22Extension; - … - } - ``` - -## ink! Integration - -See the example contract in [`lib.rs`](lib.rs). - -## Disclaimer - -:warning: This is not a feature-complete or production-ready PSP22 implementation. This -example currently lacks proper error management, precise weight accounting, tests (these -all might be added at a later point). diff --git a/integration-tests/psp22-extension/lib.rs b/integration-tests/psp22-extension/lib.rs deleted file mode 100755 index 3691ca280f4..00000000000 --- a/integration-tests/psp22-extension/lib.rs +++ /dev/null @@ -1,265 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use ink::{ - env::Environment, - prelude::vec::Vec, -}; - -type DefaultAccountId = ::AccountId; -type DefaultBalance = ::Balance; - -#[ink::chain_extension(extension = 13)] -pub trait Psp22Extension { - type ErrorCode = Psp22Error; - - // PSP22 Metadata interfaces - - #[ink(function = 0x3d26)] - fn token_name(asset_id: u32) -> Result>; - - #[ink(function = 0x3420)] - fn token_symbol(asset_id: u32) -> Result>; - - #[ink(function = 0x7271)] - fn token_decimals(asset_id: u32) -> Result; - - // PSP22 interface queries - - #[ink(function = 0x162d)] - fn total_supply(asset_id: u32) -> Result; - - #[ink(function = 0x6568)] - fn balance_of(asset_id: u32, owner: DefaultAccountId) -> Result; - - #[ink(function = 0x4d47)] - fn allowance( - asset_id: u32, - owner: DefaultAccountId, - spender: DefaultAccountId, - ) -> Result; - - // PSP22 transfer - #[ink(function = 0xdb20)] - fn transfer(asset_id: u32, to: DefaultAccountId, value: DefaultBalance) - -> Result<()>; - - // PSP22 transfer_from - #[ink(function = 0x54b3)] - fn transfer_from( - asset_id: u32, - from: DefaultAccountId, - to: DefaultAccountId, - value: DefaultBalance, - ) -> Result<()>; - - // PSP22 approve - #[ink(function = 0xb20f)] - fn approve( - asset_id: u32, - spender: DefaultAccountId, - value: DefaultBalance, - ) -> Result<()>; - - // PSP22 increase_allowance - #[ink(function = 0x96d6)] - fn increase_allowance( - asset_id: u32, - spender: DefaultAccountId, - value: DefaultBalance, - ) -> Result<()>; - - // PSP22 decrease_allowance - #[ink(function = 0xfecb)] - fn decrease_allowance( - asset_id: u32, - spender: DefaultAccountId, - value: DefaultBalance, - ) -> Result<()>; -} - -#[derive(Debug, PartialEq, Eq)] -#[ink::scale_derive(Encode, Decode, TypeInfo)] -pub enum Psp22Error { - TotalSupplyFailed, -} - -pub type Result = core::result::Result; - -impl From for Psp22Error { - fn from(_: ink::scale::Error) -> Self { - panic!("encountered unexpected invalid SCALE encoding") - } -} - -impl ink::env::chain_extension::FromStatusCode for Psp22Error { - fn from_status_code(status_code: u32) -> core::result::Result<(), Self> { - match status_code { - 0 => Ok(()), - 1 => Err(Self::TotalSupplyFailed), - _ => panic!("encountered unknown status code"), - } - } -} - -/// An environment using default ink environment types, with PSP-22 extension included -#[derive(Debug, Clone, PartialEq, Eq)] -#[ink::scale_derive(TypeInfo)] -pub enum CustomEnvironment {} - -impl Environment for CustomEnvironment { - const MAX_EVENT_TOPICS: usize = - ::MAX_EVENT_TOPICS; - - type AccountId = DefaultAccountId; - type Balance = DefaultBalance; - type Hash = ::Hash; - type Timestamp = ::Timestamp; - type BlockNumber = ::BlockNumber; - - type ChainExtension = crate::Psp22Extension; -} - -#[ink::contract(env = crate::CustomEnvironment)] -mod psp22_ext { - use super::{ - Result, - Vec, - }; - - /// A chain extension which implements the PSP-22 fungible token standard. - /// For more details see - #[ink(storage)] - #[derive(Default)] - pub struct Psp22Extension {} - - impl Psp22Extension { - /// Creates a new instance of this contract. - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - // PSP22 Metadata interfaces - - /// Returns the token name of the specified asset. - #[ink(message, selector = 0x3d261bd4)] - pub fn token_name(&self, asset_id: u32) -> Result> { - self.env().extension().token_name(asset_id) - } - - /// Returns the token symbol of the specified asset. - #[ink(message, selector = 0x34205be5)] - pub fn token_symbol(&self, asset_id: u32) -> Result> { - self.env().extension().token_symbol(asset_id) - } - - /// Returns the token decimals of the specified asset. - #[ink(message, selector = 0x7271b782)] - pub fn token_decimals(&self, asset_id: u32) -> Result { - self.env().extension().token_decimals(asset_id) - } - - // PSP22 interface queries - - /// Returns the total token supply of the specified asset. - #[ink(message, selector = 0x162df8c2)] - pub fn total_supply(&self, asset_id: u32) -> Result { - self.env().extension().total_supply(asset_id) - } - - /// Returns the account balance for the specified asset & owner. - #[ink(message, selector = 0x6568382f)] - pub fn balance_of(&self, asset_id: u32, owner: AccountId) -> Result { - self.env().extension().balance_of(asset_id, owner) - } - - /// Returns the amount which `spender` is still allowed to withdraw from `owner` - /// for the specified asset. - #[ink(message, selector = 0x4d47d921)] - pub fn allowance( - &self, - asset_id: u32, - owner: AccountId, - spender: AccountId, - ) -> Result { - self.env().extension().allowance(asset_id, owner, spender) - } - - // PSP22 transfer - - /// Transfers `value` amount of specified asset from the caller's account to the - /// account `to`. - #[ink(message, selector = 0xdb20f9f5)] - pub fn transfer( - &mut self, - asset_id: u32, - to: AccountId, - value: Balance, - ) -> Result<()> { - self.env().extension().transfer(asset_id, to, value) - } - - // PSP22 transfer_from - - /// Transfers `value` amount of specified asset on the behalf of `from` to the - /// account `to`. - #[ink(message, selector = 0x54b3c76e)] - pub fn transfer_from( - &mut self, - asset_id: u32, - from: AccountId, - to: AccountId, - value: Balance, - ) -> Result<()> { - self.env() - .extension() - .transfer_from(asset_id, from, to, value) - } - - // PSP22 approve - - /// Allows `spender` to withdraw from the caller's account multiple times, up to - /// the `value` amount of the specified asset. - #[ink(message, selector = 0xb20f1bbd)] - pub fn approve( - &mut self, - asset_id: u32, - spender: AccountId, - value: Balance, - ) -> Result<()> { - self.env().extension().approve(asset_id, spender, value) - } - - // PSP22 increase_allowance - - /// Atomically increases the allowance for the specified asset granted to - /// `spender` by the caller. - #[ink(message, selector = 0x96d6b57a)] - pub fn increase_allowance( - &mut self, - asset_id: u32, - spender: AccountId, - value: Balance, - ) -> Result<()> { - self.env() - .extension() - .increase_allowance(asset_id, spender, value) - } - - // PSP22 decrease_allowance - - /// Atomically decreases the allowance for the specified asset granted to - /// `spender` by the caller. - #[ink(message, selector = 0xfecb57d5)] - pub fn decrease_allowance( - &mut self, - asset_id: u32, - spender: AccountId, - value: Balance, - ) -> Result<()> { - self.env() - .extension() - .decrease_allowance(asset_id, spender, value) - } - } -} diff --git a/integration-tests/psp22-extension/runtime/psp22-extension-example.rs b/integration-tests/psp22-extension/runtime/psp22-extension-example.rs deleted file mode 100644 index aa11d819cdb..00000000000 --- a/integration-tests/psp22-extension/runtime/psp22-extension-example.rs +++ /dev/null @@ -1,446 +0,0 @@ -use codec::{ - Decode, - Encode, - MaxEncodedLen, -}; -use frame_support::{ - dispatch::RawOrigin, - log::{ - error, - trace, - }, - pallet_prelude::*, - traits::fungibles::{ - approvals::{ - Inspect as AllowanceInspect, - Mutate as AllowanceMutate, - }, - Inspect, - InspectMetadata, - Transfer, - }, -}; -use pallet_assets::{ - self, - WeightInfo, -}; -use pallet_contracts::chain_extension::{ - ChainExtension, - Environment, - Ext, - InitState, - RetVal, - SysConfig, - UncheckedFrom, -}; -use sp_runtime::{ - traits::{ - Saturating, - StaticLookup, - Zero, - }, - DispatchError, -}; - -#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] -struct Psp22BalanceOfInput { - asset_id: AssetId, - owner: AccountId, -} - -#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] -struct Psp22AllowanceInput { - asset_id: AssetId, - owner: AccountId, - spender: AccountId, -} - -#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] -struct Psp22TransferInput { - asset_id: AssetId, - to: AccountId, - value: Balance, -} - -#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] -struct Psp22TransferFromInput { - asset_id: AssetId, - from: AccountId, - to: AccountId, - value: Balance, -} - -#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] -struct Psp22ApproveInput { - asset_id: AssetId, - spender: AccountId, - value: Balance, -} - -#[derive(Default)] -pub struct Psp22Extension; - -fn convert_err(err_msg: &'static str) -> impl FnOnce(DispatchError) -> DispatchError { - move |err| { - trace!( - target: "runtime", - "PSP22 Transfer failed:{:?}", - err - ); - DispatchError::Other(err_msg) - } -} - -/// We're using enums for function IDs because contrary to raw u16 it enables -/// exhaustive matching, which results in cleaner code. -enum FuncId { - Metadata(Metadata), - Query(Query), - Transfer, - TransferFrom, - Approve, - IncreaseAllowance, - DecreaseAllowance, -} - -#[derive(Debug)] -enum Metadata { - Name, - Symbol, - Decimals, -} - -#[derive(Debug)] -enum Query { - TotalSupply, - BalanceOf, - Allowance, -} - -impl TryFrom for FuncId { - type Error = DispatchError; - - fn try_from(func_id: u16) -> Result { - let id = match func_id { - // Note: We use the first two bytes of PSP22 interface selectors as function - // IDs, While we can use anything here, it makes sense from a - // convention perspective. - 0x3d26 => Self::Metadata(Metadata::Name), - 0x3420 => Self::Metadata(Metadata::Symbol), - 0x7271 => Self::Metadata(Metadata::Decimals), - 0x162d => Self::Query(Query::TotalSupply), - 0x6568 => Self::Query(Query::BalanceOf), - 0x4d47 => Self::Query(Query::Allowance), - 0xdb20 => Self::Transfer, - 0x54b3 => Self::TransferFrom, - 0xb20f => Self::Approve, - 0x96d6 => Self::IncreaseAllowance, - 0xfecb => Self::DecreaseAllowance, - _ => { - error!("Called an unregistered `func_id`: {:}", func_id); - return Err(DispatchError::Other("Unimplemented func_id")) - } - }; - - Ok(id) - } -} - -fn metadata( - func_id: Metadata, - env: Environment, -) -> Result<(), DispatchError> -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - E: Ext, -{ - let mut env = env.buf_in_buf_out(); - let asset_id = env.read_as()?; - let result = match func_id { - Metadata::Name => { - as InspectMetadata>::name(&asset_id) - .encode() - } - Metadata::Symbol => { - as InspectMetadata>::symbol(&asset_id) - .encode() - } - Metadata::Decimals => { - as InspectMetadata>::decimals( - &asset_id, - ) - .encode() - } - }; - trace!( - target: "runtime", - "[ChainExtension] PSP22Metadata::{:?}", - func_id - ); - env.write(&result, false, None) - .map_err(convert_err("ChainExtension failed to call PSP22Metadata")) -} - -fn query( - func_id: Query, - env: Environment, -) -> Result<(), DispatchError> -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - E: Ext, -{ - let mut env = env.buf_in_buf_out(); - let result = match func_id { - Query::TotalSupply => { - let asset_id = env.read_as()?; - as Inspect>::total_issuance(asset_id) - } - Query::BalanceOf => { - let input: Psp22BalanceOfInput = env.read_as()?; - as Inspect>::balance( - input.asset_id, - &input.owner, - ) - } - Query::Allowance => { - let input: Psp22AllowanceInput = env.read_as()?; - as AllowanceInspect>::allowance( - input.asset_id, - &input.owner, - &input.spender, - ) - } - } - .encode(); - trace!( - target: "runtime", - "[ChainExtension] PSP22::{:?}", - func_id - ); - env.write(&result, false, None) - .map_err(convert_err("ChainExtension failed to call PSP22 query")) -} - -fn transfer(env: Environment) -> Result<(), DispatchError> -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - E: Ext, -{ - let mut env = env.buf_in_buf_out(); - let base_weight = ::WeightInfo::transfer(); - // debug_message weight is a good approximation of the additional overhead of going - // from contract layer to substrate layer. - let overhead = Weight::from_ref_time( - ::Schedule::get() - .host_fn_weights - .debug_message, - ); - let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; - trace!( - target: "runtime", - "[ChainExtension]|call|transfer / charge_weight:{:?}", - charged_weight - ); - - let input: Psp22TransferInput = - env.read_as()?; - let sender = env.ext().caller(); - - as Transfer>::transfer( - input.asset_id, - sender, - &input.to, - input.value, - true, - ) - .map_err(convert_err("ChainExtension failed to call transfer"))?; - trace!( - target: "runtime", - "[ChainExtension]|call|transfer" - ); - - Ok(()) -} - -fn transfer_from(env: Environment) -> Result<(), DispatchError> -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - E: Ext, -{ - let mut env = env.buf_in_buf_out(); - let base_weight = ::WeightInfo::transfer(); - // debug_message weight is a good approximation of the additional overhead of going - // from contract layer to substrate layer. - let overhead = Weight::from_ref_time( - ::Schedule::get() - .host_fn_weights - .debug_message, - ); - let charged_amount = env.charge_weight(base_weight.saturating_add(overhead))?; - trace!( - target: "runtime", - "[ChainExtension]|call|transfer / charge_weight:{:?}", - charged_amount - ); - - let input: Psp22TransferFromInput = - env.read_as()?; - let spender = env.ext().caller(); - - let result = - as AllowanceMutate>::transfer_from( - input.asset_id, - &input.from, - spender, - &input.to, - input.value, - ); - trace!( - target: "runtime", - "[ChainExtension]|call|transfer_from" - ); - result.map_err(convert_err("ChainExtension failed to call transfer_from")) -} - -fn approve(env: Environment) -> Result<(), DispatchError> -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - E: Ext, -{ - let mut env = env.buf_in_buf_out(); - let base_weight = ::WeightInfo::approve_transfer(); - // debug_message weight is a good approximation of the additional overhead of going - // from contract layer to substrate layer. - let overhead = Weight::from_ref_time( - ::Schedule::get() - .host_fn_weights - .debug_message, - ); - let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; - trace!( - target: "runtime", - "[ChainExtension]|call|approve / charge_weight:{:?}", - charged_weight - ); - - let input: Psp22ApproveInput = env.read_as()?; - let owner = env.ext().caller(); - - let result = as AllowanceMutate>::approve( - input.asset_id, - owner, - &input.spender, - input.value, - ); - trace!( - target: "runtime", - "[ChainExtension]|call|approve" - ); - result.map_err(convert_err("ChainExtension failed to call approve")) -} - -fn decrease_allowance(env: Environment) -> Result<(), DispatchError> -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - E: Ext, -{ - let mut env = env.buf_in_buf_out(); - let input: Psp22ApproveInput = env.read_as()?; - if input.value.is_zero() { - return Ok(()) - } - - let base_weight = ::WeightInfo::cancel_approval() - .saturating_add(::WeightInfo::approve_transfer()); - // debug_message weight is a good approximation of the additional overhead of going - // from contract layer to substrate layer. - let overhead = Weight::from_ref_time( - ::Schedule::get() - .host_fn_weights - .debug_message, - ); - let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; - trace!( - target: "runtime", - "[ChainExtension]|call|decrease_allowance / charge_weight:{:?}", - charged_weight - ); - - let owner = env.ext().caller(); - let mut allowance = - as AllowanceInspect>::allowance( - input.asset_id, - owner, - &input.spender, - ); - >::cancel_approval( - RawOrigin::Signed(owner.clone()).into(), - input.asset_id, - T::Lookup::unlookup(input.spender.clone()), - ) - .map_err(convert_err( - "ChainExtension failed to call decrease_allowance", - ))?; - allowance.saturating_reduce(input.value); - if allowance.is_zero() { - // If reduce value was less or equal than existing allowance, it should stay none. - env.adjust_weight( - charged_weight, - ::WeightInfo::cancel_approval() - .saturating_add(overhead), - ); - return Ok(()) - } - as AllowanceMutate>::approve( - input.asset_id, - owner, - &input.spender, - allowance, - ) - .map_err(convert_err( - "ChainExtension failed to call decrease_allowance", - ))?; - - trace!( - target: "runtime", - "[ChainExtension]|call|decrease_allowance" - ); - - Ok(()) -} - -impl ChainExtension for Psp22Extension -where - T: pallet_assets::Config + pallet_contracts::Config, - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, -{ - fn call( - &mut self, - env: Environment, - ) -> Result - where - E: Ext, - ::AccountId: - UncheckedFrom<::Hash> + AsRef<[u8]>, - { - let func_id = FuncId::try_from(env.func_id())?; - match func_id { - FuncId::Metadata(func_id) => metadata::(func_id, env)?, - FuncId::Query(func_id) => query::(func_id, env)?, - FuncId::Transfer => transfer::(env)?, - FuncId::TransferFrom => transfer_from::(env)?, - // This is a bit of a shortcut. It was made because the documentation - // for Mutate::approve does not specify the result of subsequent calls. - FuncId::Approve | FuncId::IncreaseAllowance => approve::(env)?, - FuncId::DecreaseAllowance => decrease_allowance(env)?, - } - - Ok(RetVal::Converging(0)) - } -} diff --git a/integration-tests/rand-extension/.gitignore b/integration-tests/rand-extension/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/rand-extension/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/rand-extension/Cargo.toml b/integration-tests/rand-extension/Cargo.toml deleted file mode 100755 index 7b2f447dc5e..00000000000 --- a/integration-tests/rand-extension/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "rand_extension" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/rand-extension/README.md b/integration-tests/rand-extension/README.md deleted file mode 100644 index 19d4cc48c7a..00000000000 --- a/integration-tests/rand-extension/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Chain Extension Example - -## What is this example about? - -It demonstrates how to call a custom Substrate function from ink!. - -See [this chapter](https://use.ink/macros-attributes/chain-extension) -in our ink! documentation for more details. - -There are two parts to this example: - -* Defining and calling the extension in ink!. -* Defining the extension in Substrate. - -## Chain-side Integration - -To integrate this example into Substrate you need to do two things: - -* Use the code in [`chain-extension-example.rs`](runtime/chain-extension-example.rs) - as an implementation for the trait `ChainExtension` in Substrate. - You can just copy/paste the content of that file into e.g. your `runtime/src/lib.rs`. - -* Use the implementation as the associated type `ChainExtension` of the trait - `pallet_contracts::Config`: - ```rust - impl pallet_contracts::Config for Runtime { - … - type ChainExtension = FetchRandomExtension; - … - } - ``` - -## ink! Integration - -See the example contract in [`lib.rs`](lib.rs). diff --git a/integration-tests/rand-extension/lib.rs b/integration-tests/rand-extension/lib.rs deleted file mode 100755 index 4fad0867c39..00000000000 --- a/integration-tests/rand-extension/lib.rs +++ /dev/null @@ -1,160 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use ink::env::Environment; - -/// This is an example of how an ink! contract may call the Substrate -/// runtime function `RandomnessCollectiveFlip::random_seed`. See the -/// file `runtime/chain-extension-example.rs` for that implementation. -/// -/// Here we define the operations to interact with the Substrate runtime. -#[ink::chain_extension(extension = 666)] -pub trait FetchRandom { - type ErrorCode = RandomReadErr; - - /// Note: this gives the operation a corresponding `func_id` (1101 in this case), - /// and the chain-side chain extension will get the `func_id` to do further - /// operations. - #[ink(function = 1101)] - fn fetch_random(subject: [u8; 32]) -> [u8; 32]; -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[ink::scale_derive(Encode, Decode, TypeInfo)] -pub enum RandomReadErr { - FailGetRandomSource, -} - -impl ink::env::chain_extension::FromStatusCode for RandomReadErr { - fn from_status_code(status_code: u32) -> Result<(), Self> { - match status_code { - 0 => Ok(()), - 1 => Err(Self::FailGetRandomSource), - _ => panic!("encountered unknown status code"), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[ink::scale_derive(TypeInfo)] -pub enum CustomEnvironment {} - -impl Environment for CustomEnvironment { - const MAX_EVENT_TOPICS: usize = - ::MAX_EVENT_TOPICS; - - type AccountId = ::AccountId; - type Balance = ::Balance; - type Hash = ::Hash; - type BlockNumber = ::BlockNumber; - type Timestamp = ::Timestamp; - - type ChainExtension = FetchRandom; -} - -#[ink::contract(env = crate::CustomEnvironment)] -mod rand_extension { - use super::RandomReadErr; - - /// Defines the storage of our contract. - /// - /// Here we store the random seed fetched from the chain. - #[ink(storage)] - pub struct RandExtension { - /// Stores a single `bool` value on the storage. - value: [u8; 32], - } - - #[ink(event)] - pub struct RandomUpdated { - #[ink(topic)] - new: [u8; 32], - } - - impl RandExtension { - /// Constructor that initializes the `bool` value to the given `init_value`. - #[ink(constructor)] - pub fn new(init_value: [u8; 32]) -> Self { - Self { value: init_value } - } - - /// Constructor that initializes the `bool` value to `false`. - /// - /// Constructors may delegate to other constructors. - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - /// Seed a random value by passing some known argument `subject` to the runtime's - /// random source. Then, update the current `value` stored in this contract with - /// the new random value. - #[ink(message)] - pub fn update(&mut self, subject: [u8; 32]) -> Result<(), RandomReadErr> { - // Get the on-chain random seed - let new_random = self.env().extension().fetch_random(subject)?; - self.value = new_random; - // Emit the `RandomUpdated` event when the random seed - // is successfully fetched. - self.env().emit_event(RandomUpdated { new: new_random }); - Ok(()) - } - - /// Simply returns the current value. - #[ink(message)] - pub fn get(&self) -> [u8; 32] { - self.value - } - } - - /// Unit tests in Rust are normally defined within such a `#[cfg(test)]` - #[cfg(test)] - mod tests { - /// Imports all the definitions from the outer scope so we can use them here. - use super::*; - - /// We test if the default constructor does its job. - #[ink::test] - fn default_works() { - let rand_extension = RandExtension::new_default(); - assert_eq!(rand_extension.get(), [0; 32]); - } - - #[ink::test] - fn chain_extension_works() { - // given - struct MockedRandExtension; - impl ink::env::test::ChainExtension for MockedRandExtension { - /// The static function id of the chain extension. - fn ext_id(&self) -> u32 { - 666 - } - - /// The chain extension is called with the given input. - /// - /// Returns an error code and may fill the `output` buffer with a - /// SCALE encoded result. The error code is taken from the - /// `ink::env::chain_extension::FromStatusCode` implementation for - /// `RandomReadErr`. - fn call( - &mut self, - _func_id: u16, - _input: &[u8], - output: &mut Vec, - ) -> u32 { - let ret: [u8; 32] = [1; 32]; - ink::scale::Encode::encode_to(&ret, output); - 0 - } - } - ink::env::test::register_chain_extension(MockedRandExtension); - let mut rand_extension = RandExtension::new_default(); - assert_eq!(rand_extension.get(), [0; 32]); - - // when - rand_extension.update([0_u8; 32]).expect("update must work"); - - // then - assert_eq!(rand_extension.get(), [1; 32]); - } - } -} diff --git a/integration-tests/rand-extension/runtime/chain-extension-example.rs b/integration-tests/rand-extension/runtime/chain-extension-example.rs deleted file mode 100644 index 4d4a4a9bcbe..00000000000 --- a/integration-tests/rand-extension/runtime/chain-extension-example.rs +++ /dev/null @@ -1,58 +0,0 @@ -use codec::Encode; -use frame_support::log::{ - error, - trace, -}; -use pallet_contracts::chain_extension::{ - ChainExtension, - Environment, - Ext, - InitState, - RetVal, - SysConfig, -}; -use sp_core::crypto::UncheckedFrom; -use sp_runtime::DispatchError; - -/// Contract extension for `FetchRandom` -#[derive(Default)] -pub struct FetchRandomExtension; - -impl ChainExtension for FetchRandomExtension { - fn call( - &mut self, - env: Environment, - ) -> Result - where - ::AccountId: - UncheckedFrom<::Hash> + AsRef<[u8]>, - { - let func_id = env.func_id(); - match func_id { - 1101 => { - let mut env = env.buf_in_buf_out(); - let arg: [u8; 32] = env.read_as()?; - let random_seed = crate::RandomnessCollectiveFlip::random(&arg).0; - let random_slice = random_seed.encode(); - trace!( - target: "runtime", - "[ChainExtension]|call|func_id:{:}", - func_id - ); - env.write(&random_slice, false, None).map_err(|_| { - DispatchError::Other("ChainExtension failed to call random") - })?; - } - - _ => { - error!("Called an unregistered `func_id`: {:}", func_id); - return Err(DispatchError::Other("Unimplemented func_id")) - } - } - Ok(RetVal::Converging(0)) - } - - fn enabled() -> bool { - true - } -} diff --git a/integration-tests/trait-dyn-cross-contract-calls/Cargo.toml b/integration-tests/trait-dyn-cross-contract-calls/Cargo.toml deleted file mode 100644 index 45dd72e9712..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "trait-incrementer-caller" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -dyn-traits = { path = "./traits", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } -trait-incrementer = { path = "./contracts/incrementer", default-features = false, features = ["ink-as-dependency"] } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "dyn-traits/std", -] -e2e-tests = [] -ink-as-dependency = [] - -# Required to be able to run e2e test with sub-contracts -[workspace] -members = [ - "contracts/incrementer", -] diff --git a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml deleted file mode 100644 index b4d45355e76..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "trait-incrementer" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../../crates/ink", default-features = false } - -dyn-traits = { path = "../../traits", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "dyn-traits/std", -] -ink-as-dependency = [] diff --git a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs deleted file mode 100644 index 7ef3912b9af..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::contract] -pub mod incrementer { - use dyn_traits::Increment; - - /// A concrete incrementer smart contract. - #[ink(storage)] - pub struct Incrementer { - value: u64, - } - - impl Incrementer { - /// Creates a new incrementer smart contract initialized with zero. - #[ink(constructor)] - pub fn new() -> Self { - Self { - value: u64::default(), - } - } - - /// Increases the value of the incrementer by an amount. - #[ink(message)] - pub fn inc_by(&mut self, delta: u64) { - self.value = self.value.checked_add(delta).unwrap(); - } - } - - impl Increment for Incrementer { - #[ink(message)] - fn inc(&mut self) { - self.inc_by(1) - } - - #[ink(message)] - fn get(&self) -> u64 { - self.value - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn it_works() { - let mut incrementer = Incrementer::new(); - // Can call using universal call syntax using the trait. - assert_eq!(::get(&incrementer), 0); - ::inc(&mut incrementer); - // Normal call syntax possible to as long as the trait is in scope. - assert_eq!(incrementer.get(), 1); - } - } -} diff --git a/integration-tests/trait-dyn-cross-contract-calls/lib.rs b/integration-tests/trait-dyn-cross-contract-calls/lib.rs deleted file mode 100644 index 2f0adad9498..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/lib.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! This crate contains the `Caller` contract with no functionality except forwarding -//! all calls to the `trait_incrementer::Incrementer` contract. -//! -//! The `Caller` doesn't use the `trait_incrementer::IncrementerRef`. Instead, -//! all interactions with the `Incrementer` is done through the wrapper from -//! `ink::contract_ref!` and the trait `dyn_traits::Increment`. -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::contract] -pub mod caller { - use dyn_traits::Increment; - - /// The caller of the incrementer smart contract. - #[ink(storage)] - pub struct Caller { - /// Here we accept a type which implements the `Incrementer` ink! trait. - incrementer: ink::contract_ref!(Increment), - } - - impl Caller { - /// Creates a new caller smart contract around the `incrementer` account id. - #[ink(constructor)] - pub fn new(incrementer: AccountId) -> Self { - Self { - incrementer: incrementer.into(), - } - } - } - - impl Increment for Caller { - #[ink(message)] - fn inc(&mut self) { - self.incrementer.inc() - } - - #[ink(message)] - fn get(&self) -> u64 { - self.incrementer.get() - } - } -} - -#[cfg(all(test, feature = "e2e-tests"))] -mod e2e_tests { - use super::caller::{ - Caller, - CallerRef, - }; - use dyn_traits::Increment; - use ink_e2e::ContractsBackend; - use trait_incrementer::incrementer::{ - Incrementer, - IncrementerRef, - }; - - type E2EResult = Result>; - - /// A test deploys and instantiates the `trait_incrementer::Incrementer` and - /// `trait_incrementer_caller::Caller` contracts, where the `Caller` uses the account - /// id of the `Incrementer` for instantiation. - /// - /// The test verifies that we can increment the value of the `Incrementer` contract - /// through the `Caller` contract. - #[ink_e2e::test(additional_contracts = "contracts/incrementer/Cargo.toml")] - async fn e2e_cross_contract_calls( - mut client: Client, - ) -> E2EResult<()> { - let _ = client - .upload("trait-incrementer", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `trait-incrementer` failed") - .code_hash; - - let _ = client - .upload("trait-incrementer-caller", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `trait-incrementer-caller` failed") - .code_hash; - - let mut constructor = IncrementerRef::new(); - - let incrementer = client - .instantiate("trait-incrementer", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let incrementer_call = incrementer.call::(); - - let mut constructor = CallerRef::new(incrementer.account_id.clone()); - - let caller = client - .instantiate( - "trait-incrementer-caller", - &ink_e2e::alice(), - &mut constructor, - ) - .submit() - .await - .expect("instantiate failed"); - let mut caller_call = caller.call::(); - - // Check through the caller that the value of the incrementer is zero - let get = caller_call.get(); - let value = client - .call(&ink_e2e::alice(), &get) - .dry_run() - .await? - .return_value(); - assert_eq!(value, 0); - - // Increment the value of the incrementer via the caller - let inc = caller_call.inc(); - let _ = client - .call(&ink_e2e::alice(), &inc) - .submit() - .await - .expect("calling `inc` failed"); - - // Ask the `trait-increment` about a value. It should be updated by the caller. - // Also use `contract_ref!(Increment)` instead of `IncrementerRef` - // to check that it also works with e2e testing. - let get = incrementer_call.get(); - let value = client - .call(&ink_e2e::alice(), &get) - .dry_run() - .await? - .return_value(); - assert_eq!(value, 1); - - Ok(()) - } -} diff --git a/integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore b/integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml b/integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml deleted file mode 100644 index 46e93afbef9..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "dyn-traits" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs b/integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs deleted file mode 100644 index bf3c0aa0f1d..00000000000 --- a/integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -//! The trait is extracted into a separate crate to show how to do cross-contract -//! calls only with traits without importing the contract. - -/// Allows to increment and get the current value. -#[ink::trait_definition] -pub trait Increment { - /// Increments the current value of the implementer by one (1). - #[ink(message)] - fn inc(&mut self); - - /// Returns the current value of the implementer. - #[ink(message)] - fn get(&self) -> u64; -} diff --git a/integration-tests/trait-erc20/.gitignore b/integration-tests/trait-erc20/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/trait-erc20/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-erc20/Cargo.toml b/integration-tests/trait-erc20/Cargo.toml deleted file mode 100644 index 3582068e689..00000000000 --- a/integration-tests/trait-erc20/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "trait_erc20" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/trait-erc20/lib.rs b/integration-tests/trait-erc20/lib.rs deleted file mode 100644 index 56c9c739751..00000000000 --- a/integration-tests/trait-erc20/lib.rs +++ /dev/null @@ -1,549 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod erc20 { - use ink::storage::Mapping; - - /// The ERC-20 error types. - #[derive(Debug, PartialEq, Eq)] - #[ink::scale_derive(Encode, Decode, TypeInfo)] - pub enum Error { - /// Returned if not enough balance to fulfill a request is available. - InsufficientBalance, - /// Returned if not enough allowance to fulfill a request is available. - InsufficientAllowance, - } - - /// The ERC-20 result type. - pub type Result = core::result::Result; - - /// Trait implemented by all ERC-20 respecting smart contracts. - #[ink::trait_definition] - pub trait BaseErc20 { - /// Returns the total token supply. - #[ink(message)] - fn total_supply(&self) -> Balance; - - /// Returns the account balance for the specified `owner`. - #[ink(message)] - fn balance_of(&self, owner: AccountId) -> Balance; - - /// Returns the amount which `spender` is still allowed to withdraw from `owner`. - #[ink(message)] - fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance; - - /// Transfers `value` amount of tokens from the caller's account to account `to`. - #[ink(message)] - fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()>; - - /// Allows `spender` to withdraw from the caller's account multiple times, up to - /// the `value` amount. - #[ink(message)] - fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()>; - - /// Transfers `value` tokens on the behalf of `from` to the account `to`. - #[ink(message)] - fn transfer_from( - &mut self, - from: AccountId, - to: AccountId, - value: Balance, - ) -> Result<()>; - } - - /// A simple ERC-20 contract. - #[ink(storage)] - #[derive(Default)] - pub struct Erc20 { - /// Total token supply. - total_supply: Balance, - /// Mapping from owner to number of owned token. - balances: Mapping, - /// Mapping of the token amount which an account is allowed to withdraw - /// from another account. - allowances: Mapping<(AccountId, AccountId), Balance>, - } - - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - #[ink(topic)] - value: Balance, - } - - /// Event emitted when an approval occurs that `spender` is allowed to withdraw - /// up to the amount of `value` tokens from `owner`. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - spender: AccountId, - #[ink(topic)] - value: Balance, - } - - impl Erc20 { - /// Creates a new ERC-20 contract with the specified initial supply. - #[ink(constructor)] - pub fn new(total_supply: Balance) -> Self { - let mut balances = Mapping::default(); - let caller = Self::env().caller(); - balances.insert(caller, &total_supply); - Self::env().emit_event(Transfer { - from: None, - to: Some(caller), - value: total_supply, - }); - Self { - total_supply, - balances, - allowances: Default::default(), - } - } - } - - impl BaseErc20 for Erc20 { - /// Returns the total token supply. - #[ink(message)] - fn total_supply(&self) -> Balance { - self.total_supply - } - - /// Returns the account balance for the specified `owner`. - /// - /// Returns `0` if the account is non-existent. - #[ink(message)] - fn balance_of(&self, owner: AccountId) -> Balance { - self.balance_of_impl(&owner) - } - - /// Returns the amount which `spender` is still allowed to withdraw from `owner`. - /// - /// Returns `0` if no allowance has been set. - #[ink(message)] - fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { - self.allowance_impl(&owner, &spender) - } - - /// Transfers `value` amount of tokens from the caller's account to account `to`. - /// - /// On success a `Transfer` event is emitted. - /// - /// # Errors - /// - /// Returns `InsufficientBalance` error if there are not enough tokens on - /// the caller's account balance. - #[ink(message)] - fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> { - let from = self.env().caller(); - self.transfer_from_to(&from, &to, value) - } - - /// Allows `spender` to withdraw from the caller's account multiple times, up to - /// the `value` amount. - /// - /// If this function is called again it overwrites the current allowance with - /// `value`. - /// - /// An `Approval` event is emitted. - #[ink(message)] - fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { - let owner = self.env().caller(); - self.allowances.insert((&owner, &spender), &value); - self.env().emit_event(Approval { - owner, - spender, - value, - }); - Ok(()) - } - - /// Transfers `value` tokens on the behalf of `from` to the account `to`. - /// - /// This can be used to allow a contract to transfer tokens on ones behalf and/or - /// to charge fees in sub-currencies, for example. - /// - /// On success a `Transfer` event is emitted. - /// - /// # Errors - /// - /// Returns `InsufficientAllowance` error if there are not enough tokens allowed - /// for the caller to withdraw from `from`. - /// - /// Returns `InsufficientBalance` error if there are not enough tokens on - /// the account balance of `from`. - #[ink(message)] - fn transfer_from( - &mut self, - from: AccountId, - to: AccountId, - value: Balance, - ) -> Result<()> { - let caller = self.env().caller(); - let allowance = self.allowance_impl(&from, &caller); - if allowance < value { - return Err(Error::InsufficientAllowance) - } - self.transfer_from_to(&from, &to, value)?; - // We checked that allowance >= value - #[allow(clippy::arithmetic_side_effects)] - self.allowances - .insert((&from, &caller), &(allowance - value)); - Ok(()) - } - } - - #[ink(impl)] - impl Erc20 { - /// Returns the account balance for the specified `owner`. - /// - /// Returns `0` if the account is non-existent. - /// - /// # Note - /// - /// Prefer to call this method over `balance_of` since this - /// works using references which are more efficient in Wasm. - #[inline] - fn balance_of_impl(&self, owner: &AccountId) -> Balance { - self.balances.get(owner).unwrap_or_default() - } - - /// Returns the amount which `spender` is still allowed to withdraw from `owner`. - /// - /// Returns `0` if no allowance has been set. - /// - /// # Note - /// - /// Prefer to call this method over `allowance` since this - /// works using references which are more efficient in Wasm. - #[inline] - fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance { - self.allowances.get((owner, spender)).unwrap_or_default() - } - - /// Transfers `value` amount of tokens from the caller's account to account `to`. - /// - /// On success a `Transfer` event is emitted. - /// - /// # Errors - /// - /// Returns `InsufficientBalance` error if there are not enough tokens on - /// the caller's account balance. - fn transfer_from_to( - &mut self, - from: &AccountId, - to: &AccountId, - value: Balance, - ) -> Result<()> { - let from_balance = self.balance_of_impl(from); - if from_balance < value { - return Err(Error::InsufficientBalance) - } - // We checked that from_balance >= value - #[allow(clippy::arithmetic_side_effects)] - self.balances.insert(from, &(from_balance - value)); - let to_balance = self.balance_of_impl(to); - self.balances - .insert(to, &(to_balance.checked_add(value).unwrap())); - self.env().emit_event(Transfer { - from: Some(*from), - to: Some(*to), - value, - }); - Ok(()) - } - } - - /// Unit tests. - #[cfg(test)] - mod tests { - /// Imports all the definitions from the outer scope so we can use them here. - use super::*; - use ink::{ - env::hash::{ - Blake2x256, - CryptoHash, - HashOutput, - }, - primitives::Clear, - }; - - fn assert_transfer_event( - event: &ink::env::test::EmittedEvent, - expected_from: Option, - expected_to: Option, - expected_value: Balance, - ) { - let decoded_event = - ::decode(&mut &event.data[..]) - .expect("encountered invalid contract event data buffer"); - let Transfer { from, to, value } = decoded_event; - assert_eq!(from, expected_from, "encountered invalid Transfer.from"); - assert_eq!(to, expected_to, "encountered invalid Transfer.to"); - assert_eq!(value, expected_value, "encountered invalid Trasfer.value"); - - fn encoded_into_hash(entity: T) -> Hash - where - T: ink::scale::Encode, - { - let mut result = Hash::CLEAR_HASH; - let len_result = result.as_ref().len(); - let encoded = entity.encode(); - let len_encoded = encoded.len(); - if len_encoded <= len_result { - result.as_mut()[..len_encoded].copy_from_slice(&encoded); - return result - } - let mut hash_output = - <::Type as Default>::default(); - ::hash(&encoded, &mut hash_output); - let copy_len = core::cmp::min(hash_output.len(), len_result); - result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]); - result - } - - let mut expected_topics = Vec::new(); - expected_topics.push( - ink::blake2x256!("Transfer(Option,Option,Balance)") - .into(), - ); - if let Some(from) = expected_from { - expected_topics.push(encoded_into_hash(from)); - } else { - expected_topics.push(Hash::CLEAR_HASH); - } - if let Some(to) = expected_to { - expected_topics.push(encoded_into_hash(to)); - } else { - expected_topics.push(Hash::CLEAR_HASH); - } - expected_topics.push(encoded_into_hash(value)); - - for (n, (actual_topic, expected_topic)) in - event.topics.iter().zip(expected_topics).enumerate() - { - let topic = ::decode(&mut &actual_topic[..]) - .expect("encountered invalid topic encoding"); - assert_eq!(topic, expected_topic, "encountered invalid topic at {n}"); - } - } - - /// The default constructor does its job. - #[ink::test] - fn new_works() { - // Constructor works. - let initial_supply = 100; - let erc20 = Erc20::new(initial_supply); - - // The `BaseErc20` trait has indeed been implemented. - assert_eq!(::total_supply(&erc20), initial_supply); - - // Transfer event triggered during initial construction. - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(1, emitted_events.len()); - - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - } - - /// The total supply was applied. - #[ink::test] - fn total_supply_works() { - // Constructor works. - let initial_supply = 100; - let erc20 = Erc20::new(initial_supply); - // Transfer event triggered during initial construction. - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - // Get the token total supply. - assert_eq!(erc20.total_supply(), 100); - } - - /// Get the actual balance of an account. - #[ink::test] - fn balance_of_works() { - // Constructor works - let initial_supply = 100; - let erc20 = Erc20::new(initial_supply); - // Transfer event triggered during initial construction - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - let accounts = - ink::env::test::default_accounts::(); - // Alice owns all the tokens on contract instantiation - assert_eq!(erc20.balance_of(accounts.alice), 100); - // Bob does not owns tokens - assert_eq!(erc20.balance_of(accounts.bob), 0); - } - - #[ink::test] - fn transfer_works() { - // Constructor works. - let initial_supply = 100; - let mut erc20 = Erc20::new(initial_supply); - // Transfer event triggered during initial construction. - let accounts = - ink::env::test::default_accounts::(); - - assert_eq!(erc20.balance_of(accounts.bob), 0); - // Alice transfers 10 tokens to Bob. - assert_eq!(erc20.transfer(accounts.bob, 10), Ok(())); - // Bob owns 10 tokens. - assert_eq!(erc20.balance_of(accounts.bob), 10); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 2); - // Check first transfer event related to ERC-20 instantiation. - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - // Check the second transfer event relating to the actual trasfer. - assert_transfer_event( - &emitted_events[1], - Some(AccountId::from([0x01; 32])), - Some(AccountId::from([0x02; 32])), - 10, - ); - } - - #[ink::test] - fn invalid_transfer_should_fail() { - // Constructor works. - let initial_supply = 100; - let mut erc20 = Erc20::new(initial_supply); - let accounts = - ink::env::test::default_accounts::(); - - assert_eq!(erc20.balance_of(accounts.bob), 0); - // Set Bob as caller - set_caller(accounts.bob); - - // Bob fails to transfers 10 tokens to Eve. - assert_eq!( - erc20.transfer(accounts.eve, 10), - Err(Error::InsufficientBalance) - ); - // Alice owns all the tokens. - assert_eq!(erc20.balance_of(accounts.alice), 100); - assert_eq!(erc20.balance_of(accounts.bob), 0); - assert_eq!(erc20.balance_of(accounts.eve), 0); - - // Transfer event triggered during initial construction. - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 1); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - } - - #[ink::test] - fn transfer_from_works() { - // Constructor works. - let initial_supply = 100; - let mut erc20 = Erc20::new(initial_supply); - // Transfer event triggered during initial construction. - let accounts = - ink::env::test::default_accounts::(); - - // Bob fails to transfer tokens owned by Alice. - assert_eq!( - erc20.transfer_from(accounts.alice, accounts.eve, 10), - Err(Error::InsufficientAllowance) - ); - // Alice approves Bob for token transfers on her behalf. - assert_eq!(erc20.approve(accounts.bob, 10), Ok(())); - - // The approve event takes place. - assert_eq!(ink::env::test::recorded_events().count(), 2); - - // Set Bob as caller. - set_caller(accounts.bob); - - // Bob transfers tokens from Alice to Eve. - assert_eq!( - erc20.transfer_from(accounts.alice, accounts.eve, 10), - Ok(()) - ); - // Eve owns tokens. - assert_eq!(erc20.balance_of(accounts.eve), 10); - - // Check all transfer events that happened during the previous calls: - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 3); - assert_transfer_event( - &emitted_events[0], - None, - Some(AccountId::from([0x01; 32])), - 100, - ); - // The second event `emitted_events[1]` is an Approve event that we skip - // checking. - assert_transfer_event( - &emitted_events[2], - Some(AccountId::from([0x01; 32])), - Some(AccountId::from([0x05; 32])), - 10, - ); - } - - #[ink::test] - fn allowance_must_not_change_on_failed_transfer() { - let initial_supply = 100; - let mut erc20 = Erc20::new(initial_supply); - let accounts = - ink::env::test::default_accounts::(); - - // Alice approves Bob for token transfers on her behalf. - let alice_balance = erc20.balance_of(accounts.alice); - let initial_allowance = alice_balance + 2; - assert_eq!(erc20.approve(accounts.bob, initial_allowance), Ok(())); - - // Set Bob as caller. - set_caller(accounts.bob); - - // Bob tries to transfer tokens from Alice to Eve. - let emitted_events_before = ink::env::test::recorded_events(); - assert_eq!( - erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1), - Err(Error::InsufficientBalance) - ); - // Allowance must have stayed the same - assert_eq!( - erc20.allowance(accounts.alice, accounts.bob), - initial_allowance - ); - // No more events must have been emitted - let emitted_events_after = ink::env::test::recorded_events(); - assert_eq!(emitted_events_before.count(), emitted_events_after.count()); - } - - fn set_caller(sender: AccountId) { - ink::env::test::set_caller::(sender); - } - } -} diff --git a/integration-tests/trait-flipper/.gitignore b/integration-tests/trait-flipper/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/trait-flipper/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-flipper/Cargo.toml b/integration-tests/trait-flipper/Cargo.toml deleted file mode 100644 index 02c1b0d002a..00000000000 --- a/integration-tests/trait-flipper/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "trait_flipper" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/trait-flipper/lib.rs b/integration-tests/trait-flipper/lib.rs deleted file mode 100644 index 7be1ac641ed..00000000000 --- a/integration-tests/trait-flipper/lib.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::trait_definition] -pub trait Flip { - /// Flips the current value of the Flipper's boolean. - #[ink(message)] - fn flip(&mut self); - - /// Returns the current value of the Flipper's boolean. - #[ink(message)] - fn get(&self) -> bool; -} - -#[ink::contract] -pub mod flipper { - use super::Flip; - - #[ink(storage)] - pub struct Flipper { - value: bool, - } - - impl Flipper { - /// Creates a new flipper smart contract initialized to `false`. - #[ink(constructor)] - pub fn new() -> Self { - Self { - value: Default::default(), - } - } - } - - impl Flip for Flipper { - #[ink(message)] - fn flip(&mut self) { - self.value = !self.value; - } - - #[ink(message)] - fn get(&self) -> bool { - self.value - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - let flipper = Flipper::new(); - assert!(!flipper.get()); - } - - #[ink::test] - fn it_works() { - let mut flipper = Flipper::new(); - // Can call using universal call syntax using the trait. - assert!(!::get(&flipper)); - ::flip(&mut flipper); - // Normal call syntax possible to as long as the trait is in scope. - assert!(flipper.get()); - } - } -} diff --git a/integration-tests/trait-incrementer/.gitignore b/integration-tests/trait-incrementer/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/trait-incrementer/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-incrementer/Cargo.toml b/integration-tests/trait-incrementer/Cargo.toml deleted file mode 100644 index 360a11ac2d8..00000000000 --- a/integration-tests/trait-incrementer/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "trait-incrementer" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -traits = { path = "./traits", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", - "traits/std", -] -ink-as-dependency = [] diff --git a/integration-tests/trait-incrementer/lib.rs b/integration-tests/trait-incrementer/lib.rs deleted file mode 100644 index 65edd6f9d16..00000000000 --- a/integration-tests/trait-incrementer/lib.rs +++ /dev/null @@ -1,70 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::contract] -pub mod incrementer { - use traits::{ - Increment, - Reset, - }; - - /// A concrete incrementer smart contract. - #[ink(storage)] - pub struct Incrementer { - value: u64, - } - - impl Incrementer { - /// Creates a new incrementer smart contract initialized with zero. - #[ink(constructor)] - pub fn new(init_value: u64) -> Self { - Self { value: init_value } - } - - /// Increases the value of the incrementer by an amount. - #[ink(message)] - pub fn inc_by(&mut self, delta: u64) { - self.value = self.value.checked_add(delta).unwrap(); - } - } - - impl Increment for Incrementer { - #[ink(message)] - fn inc(&mut self) { - self.inc_by(1) - } - - #[ink(message)] - fn get(&self) -> u64 { - self.value - } - } - - impl Reset for Incrementer { - #[ink(message)] - fn reset(&mut self) { - self.value = 0; - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn default_works() { - let incrementer = Incrementer::new(0); - assert_eq!(incrementer.get(), 0); - } - - #[test] - fn it_works() { - let mut incrementer = Incrementer::new(0); - // Can call using universal call syntax using the trait. - assert_eq!(::get(&incrementer), 0); - ::inc(&mut incrementer); - // Normal call syntax possible to as long as the trait is in scope. - assert_eq!(incrementer.get(), 1); - } - } -} diff --git a/integration-tests/trait-incrementer/traits/.gitignore b/integration-tests/trait-incrementer/traits/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/trait-incrementer/traits/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-incrementer/traits/Cargo.toml b/integration-tests/trait-incrementer/traits/Cargo.toml deleted file mode 100644 index be488246a3e..00000000000 --- a/integration-tests/trait-incrementer/traits/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "traits" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[lib] -name = "traits" -path = "lib.rs" -crate-type = ["rlib"] - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/trait-incrementer/traits/lib.rs b/integration-tests/trait-incrementer/traits/lib.rs deleted file mode 100644 index 23cc0244a1d..00000000000 --- a/integration-tests/trait-incrementer/traits/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -//! Traits are extracted into a separate crate to show how the user can import -//! several foreign traits and implement those for the contract. - -/// Allows to increment and get the current value. -#[ink::trait_definition] -pub trait Increment { - /// Increments the current value of the implementer by one (1). - #[ink(message)] - fn inc(&mut self); - - /// Returns the current value of the implementer. - #[ink(message)] - fn get(&self) -> u64; -} - -/// Allows to reset the current value. -#[ink::trait_definition] -pub trait Reset { - /// Resets the current value to zero. - #[ink(message)] - fn reset(&mut self); -} diff --git a/integration-tests/upgradeable-contracts/README.md b/integration-tests/upgradeable-contracts/README.md deleted file mode 100644 index 85bd7b5c81f..00000000000 --- a/integration-tests/upgradeable-contracts/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Upgradeable Contracts - -There are different ways a contract can be upgraded in ink! - -This folder illustrates some of the common and best practices to achieve upgradeability in your contracts. - -## [`set-code-hash`](set-code-hash/) - -ink! provides an ability to replace the code under the given contract's address. -This is exactly what `set_code_hash()` function does. - -However, developers needs to be mindful of storage compatibility. -You can read more about storage compatibility on [use.ink](https://use.ink/basics/upgradeable-contracts#replacing-contract-code-with-set_code_hash) - -## [Delegator](delegator/) - -Delegator patter is based around a low level cross contract call function `delegate_call`. -It allows a contract to delegate its execution to some on-chain uploaded code. - -It is different from a traditional cross-contract call -because the call is delegate to the **code**, not the contract. - -Similarly, the storage compatibility issue is also applicable here. -However, there are certain nuances associated with using `delegate_call`. - -First of all, as demonstrated in the example, if the delegated code intends to mutate the caller's storage, -a developer needs to be mindful. If the delegated code modifies layout-full storage -(i.e. it contains at least non-`Lazy`, non-`Mapping` field), the `CallFlags::TAIL_CALL` flag needs to be specified and the storage layouts must match. -This is due to the way ink! execution call stack is operated -(see [Stack Exchange Answer](https://substrate.stackexchange.com/a/3352/3098) for more explanation). - -If the delegated code only modifies `Lazy` or `Mapping` field, the keys must be identical and `CallFlags::TAIL_CALL` is optional. -This is because `Lazy` and `Mapping` interact with the storage directly instead of loading and flushing storage states. - -If your storage is completely layoutless (it only contains `Lazy` and `Mapping` fields), the order of fields and layout do not need to match for the same reason as mentioned above. - diff --git a/integration-tests/upgradeable-contracts/delegator/.gitignore b/integration-tests/upgradeable-contracts/delegator/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/upgradeable-contracts/delegator/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/upgradeable-contracts/delegator/Cargo.toml b/integration-tests/upgradeable-contracts/delegator/Cargo.toml deleted file mode 100644 index ffcc3283363..00000000000 --- a/integration-tests/upgradeable-contracts/delegator/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "delegator" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } -delegatee = { path = "delegatee", default-features = false, features = ["ink-as-dependency"] } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore b/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml b/integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml deleted file mode 100644 index d18ce25dc3d..00000000000 --- a/integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "delegatee" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs b/integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs deleted file mode 100644 index af761b5bd55..00000000000 --- a/integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -pub mod delegatee { - use ink::storage::{ - traits::ManualKey, - Mapping, - }; - #[ink(storage)] - pub struct Delegatee { - addresses: Mapping>, - counter: i32, - // Uncommenting below line will break storage compatibility. - // flag: bool, - } - - impl Delegatee { - /// When using the delegate call. You only upload the code of the delegatee - /// contract. However, the code and storage do not get initialized. - /// - /// Because of this. The constructor actually never gets called. - #[allow(clippy::new_without_default)] - #[ink(constructor)] - pub fn new() -> Self { - unreachable!( - "Constructors are not called when upgrading using `set_code_hash`." - ) - } - - /// Increments the current value. - #[ink(message)] - pub fn inc(&mut self) { - self.counter = self.counter.checked_add(2).unwrap(); - } - - /// Adds current value of counter to the `addresses` - #[ink(message)] - pub fn append_address_value(&mut self) { - let caller = self.env().caller(); - self.addresses.insert(caller, &self.counter); - } - } -} diff --git a/integration-tests/upgradeable-contracts/delegator/lib.rs b/integration-tests/upgradeable-contracts/delegator/lib.rs deleted file mode 100644 index b6d95c3a1ca..00000000000 --- a/integration-tests/upgradeable-contracts/delegator/lib.rs +++ /dev/null @@ -1,208 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -pub mod delegator { - use ink::{ - env::CallFlags, - storage::Mapping, - }; - - use ink::{ - env::{ - call::{ - build_call, - ExecutionInput, - Selector, - }, - DefaultEnvironment, - }, - storage::traits::ManualKey, - }; - - #[ink(storage)] - pub struct Delegator { - addresses: Mapping>, - counter: i32, - } - - impl Delegator { - /// Creates a new delegator smart contract initialized with the given value. - #[ink(constructor)] - pub fn new(init_value: i32) -> Self { - let v = Mapping::new(); - Self { - addresses: v, - counter: init_value, - } - } - - /// Creates a new contract with default values. - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - /// Increment the current value using delegate call. - #[ink(message)] - pub fn inc_delegate(&mut self, hash: Hash) { - let selector = ink::selector_bytes!("inc"); - let _ = build_call::() - .delegate(hash) - // We specify `CallFlags::TAIL_CALL` to use the delegatee last memory frame - // as the end of the execution cycle. - // So any mutations to `Packed` types, made by delegatee, - // will be flushed to storage. - // - // If we don't specify this flag. - // The storage state before the delegate call will be flushed to storage instead. - // See https://substrate.stackexchange.com/questions/3336/i-found-set-allow-reentry-may-have-some-problems/3352#3352 - .call_flags(CallFlags::TAIL_CALL) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::<()>() - .try_invoke(); - } - - /// Adds entry to `addresses` using delegate call. - /// Note that we don't need `CallFlags::TAIL_CALL` flag - /// because `Mapping` updates the storage instantly on-demand. - #[ink(message)] - pub fn add_entry_delegate(&mut self, hash: Hash) { - let selector = ink::selector_bytes!("append_address_value"); - let _ = build_call::() - .delegate(hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::<()>() - .try_invoke(); - } - - /// Returns the current value of the counter. - #[ink(message)] - pub fn get_counter(&self) -> i32 { - self.counter - } - - /// Returns the current value of the address. - #[ink(message)] - pub fn get_value(&self, address: AccountId) -> (AccountId, Option) { - (self.env().caller(), self.addresses.get(address)) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ - ChainBackend, - ContractsBackend, - }; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn e2e_counter_mutated( - mut client: Client, - ) -> E2EResult<()> { - // given - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let mut constructor = DelegatorRef::new_default(); - let call_builder = client - .instantiate("delegator", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("delegatee", &origin) - .submit() - .await - .expect("upload `delegatee` failed") - .code_hash; - - // when - let call_delegate = call_builder_call.inc_delegate(code_hash); - - let result = client.call(&origin, &call_delegate).submit().await; - assert!(result.is_ok(), "delegate call failed."); - - let result = client.call(&origin, &call_delegate).submit().await; - assert!(result.is_ok(), "second delegate call failed."); - - // then - let expected_value = 4; - let call = call_builder.call::(); - - let call_get = call.get_counter(); - let call_get_result = client - .call(&origin, &call_get) - .dry_run() - .await? - .return_value(); - - // This fails - assert_eq!( - call_get_result, expected_value, - "Decoded an unexpected value from the call." - ); - - Ok(()) - } - - #[ink_e2e::test] - async fn e2e_mapping_mutated( - mut client: Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - // given - let mut constructor = DelegatorRef::new(10); - let call_builder = client - .instantiate("delegator", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - let code_hash = client - .upload("delegatee", &origin) - .submit() - .await - .expect("upload `delegatee` failed") - .code_hash; - - // when - let call_delegate = call_builder_call.add_entry_delegate(code_hash); - let result = client.call(&origin, &call_delegate).submit().await; - assert!(result.is_ok(), "delegate call failed."); - - // then - - // because we initialize the counter with `10` we expect this value be - // assigned to Alice. - let expected_value = 10; - // Alice's address - let address = AccountId::from(origin.public_key().to_account_id().0); - - let call_get_value = call_builder_call.get_value(address); - let call_get_result = client - .call(&origin, &call_get_value) - .submit() - .await - .unwrap() - .return_value(); - - assert_eq!( - call_get_result, - (address, Some(expected_value)), - "Decoded an unexpected value from the call." - ); - - Ok(()) - } - } -} diff --git a/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml b/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml deleted file mode 100644 index 8303a6dc3db..00000000000 --- a/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "incrementer" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/upgradeable-contracts/set-code-hash/lib.rs b/integration-tests/upgradeable-contracts/set-code-hash/lib.rs deleted file mode 100644 index ca8ec49f6d4..00000000000 --- a/integration-tests/upgradeable-contracts/set-code-hash/lib.rs +++ /dev/null @@ -1,138 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -//! Demonstrates how to use [`set_code_hash`](https://docs.rs/ink_env/latest/ink_env/fn.set_code_hash.html) -//! to swap out the `code_hash` of an on-chain contract. -//! -//! We will swap the code of our `Incrementer` contract with that of the an `Incrementer` -//! found in the `updated_incrementer` folder. -//! -//! See the included End-to-End tests an example update workflow. - -#[ink::contract] -pub mod incrementer { - - /// Track a counter in storage. - /// - /// # Note - /// - /// Is is important to realize that after the call to `set_code_hash` the contract's - /// storage remains the same. - /// - /// If you change the storage layout in your storage struct you may introduce - /// undefined behavior to your contract! - #[ink(storage)] - #[derive(Default)] - pub struct Incrementer { - count: u32, - } - - impl Incrementer { - /// Creates a new counter smart contract initialized with the given base value. - #[ink(constructor)] - pub fn new() -> Self { - Default::default() - } - - /// Increments the counter value which is stored in the contract's storage. - #[ink(message)] - pub fn inc(&mut self) { - self.count = self.count.checked_add(1).unwrap(); - ink::env::debug_println!( - "The new count is {}, it was modified using the original contract code.", - self.count - ); - } - - /// Returns the counter value which is stored in this contract's storage. - #[ink(message)] - pub fn get(&self) -> u32 { - self.count - } - - /// Modifies the code which is used to execute calls to this contract address - /// (`AccountId`). - /// - /// We use this to upgrade the contract logic. We don't do any authorization here, - /// any caller can execute this method. - /// - /// In a production contract you would do some authorization here! - #[ink(message)] - pub fn set_code(&mut self, code_hash: Hash) { - self.env().set_code_hash(&code_hash).unwrap_or_else(|err| { - panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") - }); - ink::env::debug_println!("Switched code hash to {:?}.", code_hash); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test(additional_contracts = "./updated-incrementer/Cargo.toml")] - async fn set_code_works(mut client: Client) -> E2EResult<()> { - // Given - let mut constructor = IncrementerRef::new(); - let contract = client - .instantiate("incrementer", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let get = call.get(); - let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; - assert!(matches!(get_res.return_value(), 0)); - - let inc = call.inc(); - let _inc_result = client - .call(&ink_e2e::alice(), &inc) - .submit() - .await - .expect("`inc` failed"); - - let get = call.get(); - let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; - assert!(matches!(get_res.return_value(), 1)); - - // When - let new_code_hash = client - .upload("updated_incrementer", &ink_e2e::alice()) - .submit() - .await - .expect("uploading `updated_incrementer` failed") - .code_hash; - - let new_code_hash = new_code_hash.as_ref().try_into().unwrap(); - let set_code = call.set_code(new_code_hash); - - let _set_code_result = client - .call(&ink_e2e::alice(), &set_code) - .submit() - .await - .expect("`set_code` failed"); - - // Then - // Note that our contract's `AccountId` (so `contract_acc_id`) has stayed the - // same between updates! - let inc = call.inc(); - - let _inc_result = client - .call(&ink_e2e::alice(), &inc) - .submit() - .await - .expect("`inc` failed"); - - let get = call.get(); - let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; - - // Remember, we updated our incrementer contract to increment by `4`. - assert!(matches!(get_res.return_value(), 5)); - - Ok(()) - } - } -} diff --git a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml deleted file mode 100644 index 128a54b2a95..00000000000 --- a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "updated-incrementer" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../../../crates/ink", default-features = false } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] diff --git a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs deleted file mode 100644 index 7741181a20e..00000000000 --- a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::new_without_default)] - -#[ink::contract] -pub mod incrementer { - - /// Track a counter in storage. - /// - /// # Note - /// - /// We have kept the same storage layout as in our original `incrementer` contract. - /// - /// Had we changed `count` to, for example, an `AccountId` we would end up with - /// undefined behaviour in our contract. - #[ink(storage)] - pub struct Incrementer { - count: u32, - } - - impl Incrementer { - /// Creates a new counter smart contract initialized with the given base value. - /// - /// # Note - /// - /// When upgrading using the `set_code_hash` workflow we only need to point to a - /// contract's uploaded code hash, **not** an instantiated contract's - /// `AccountId`. - /// - /// Because of this we will never actually call the constructor of this contract. - #[ink(constructor)] - pub fn new() -> Self { - unreachable!( - "Constructors are not called when upgrading using `set_code_hash`." - ) - } - - /// Increments the counter value which is stored in the contract's storage. - /// - /// # Note - /// - /// We use a different step size (4) here than in the original `incrementer`. - #[ink(message)] - pub fn inc(&mut self) { - self.count = self.count.checked_add(4).unwrap(); - ink::env::debug_println!("The new count is {}, it was modified using the updated `new_incrementer` code.", self.count); - } - - /// Returns the counter value which is stored in this contract's storage. - #[ink(message)] - pub fn get(&self) -> u32 { - self.count - } - - /// Modifies the code which is used to execute calls to this contract address - /// (`AccountId`). - /// - /// We use this to upgrade the contract logic. We don't do any authorization here, - /// any caller can execute this method. - /// - /// In a production contract you would do some authorization here! - #[ink(message)] - pub fn set_code(&mut self, code_hash: Hash) { - self.env().set_code_hash(&code_hash).unwrap_or_else(|err| { - panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") - }); - ink::env::debug_println!("Switched code hash to {:?}.", code_hash); - } - } -} diff --git a/integration-tests/wildcard-selector/.gitignore b/integration-tests/wildcard-selector/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/wildcard-selector/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/wildcard-selector/Cargo.toml b/integration-tests/wildcard-selector/Cargo.toml deleted file mode 100644 index e8db7c0101a..00000000000 --- a/integration-tests/wildcard-selector/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "wildcard-selector" -version = "5.0.0-rc" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/wildcard-selector/lib.rs b/integration-tests/wildcard-selector/lib.rs deleted file mode 100644 index 1e99545ba34..00000000000 --- a/integration-tests/wildcard-selector/lib.rs +++ /dev/null @@ -1,163 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -pub mod wildcard_selector { - use ink::prelude::string::String; - - #[ink(storage)] - pub struct WildcardSelector {} - - impl WildcardSelector { - /// Creates a new wildcard selector smart contract. - #[ink(constructor)] - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self {} - } - - /// Wildcard selector handles messages with any selector. - #[ink(message, selector = _)] - pub fn wildcard(&mut self) { - let (_selector, _message) = - ink::env::decode_input::<([u8; 4], String)>().unwrap(); - ink::env::debug_println!( - "Wildcard selector: {:?}, message: {}", - _selector, - _message - ); - } - - /// Wildcard complement handles messages with a well-known reserved selector. - #[ink(message, selector = @)] - pub fn wildcard_complement(&mut self, _message: String) { - ink::env::debug_println!("Wildcard complement message: {}", _message); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - use ink::env::call::utils::{ - Argument, - ArgumentList, - EmptyArgumentList, - }; - - type E2EResult = std::result::Result>; - type Environment = ::Env; - - fn build_message( - account_id: AccountId, - selector: [u8; 4], - message: String, - ) -> ink_e2e::CallBuilderFinal< - Environment, - ArgumentList, EmptyArgumentList>, - (), - > { - ink::env::call::build_call::() - .call(account_id) - .exec_input( - ink::env::call::ExecutionInput::new(ink::env::call::Selector::new( - selector, - )) - .push_arg(message), - ) - .returns::<()>() - } - - #[ink_e2e::test] - async fn arbitrary_selectors_handled_by_wildcard( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = WildcardSelectorRef::new(); - let contract_acc_id = client - .instantiate("wildcard_selector", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed") - .account_id; - - // when - const ARBITRARY_SELECTOR: [u8; 4] = [0xF9, 0xF9, 0xF9, 0xF9]; - let wildcard_message = "WILDCARD_MESSAGE 1".to_string(); - let wildcard = build_message( - contract_acc_id, - ARBITRARY_SELECTOR, - wildcard_message.clone(), - ); - - let result = client - .call(&ink_e2e::bob(), &wildcard) - .submit() - .await - .expect("wildcard failed"); - - const ARBITRARY_SELECTOR_2: [u8; 4] = [0x01, 0x23, 0x45, 0x67]; - let wildcard_message2 = "WILDCARD_MESSAGE 2".to_string(); - let wildcard2 = build_message( - contract_acc_id, - ARBITRARY_SELECTOR_2, - wildcard_message2.clone(), - ); - - let result2 = client - .call(&ink_e2e::bob(), &wildcard2) - .submit() - .await - .expect("wildcard failed"); - - // then - assert!(result.debug_message().contains(&format!( - "Wildcard selector: {:?}, message: {}", - ARBITRARY_SELECTOR, wildcard_message - ))); - - assert!(result2.debug_message().contains(&format!( - "Wildcard selector: {:?}, message: {}", - ARBITRARY_SELECTOR_2, wildcard_message2 - ))); - - Ok(()) - } - - #[ink_e2e::test] - async fn wildcard_complement_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = WildcardSelectorRef::new(); - let contract_acc_id = client - .instantiate("wildcard_selector", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed") - .account_id; - - // when - let wildcard_complement_message = "WILDCARD COMPLEMENT MESSAGE".to_string(); - let wildcard = build_message( - contract_acc_id, - ink::IIP2_WILDCARD_COMPLEMENT_SELECTOR, - wildcard_complement_message.clone(), - ); - - let result = client - .call(&ink_e2e::bob(), &wildcard) - .submit() - .await - .expect("wildcard failed"); - - // then - assert!(result.debug_message().contains(&format!( - "Wildcard complement message: {}", - wildcard_complement_message - ))); - - Ok(()) - } - } -} From d8e14ae70089410eed5ba50f6080863207b8dc51 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:19:21 +0100 Subject: [PATCH 10/26] Fix CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c8eefbe2e6..af5b910431f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -488,6 +488,7 @@ jobs: scripts/for_all_contracts_exec.sh --path integration-tests --ignore static-buffer -- cargo test \ --verbose --all-features --manifest-path {} # run flipper E2E test with on-chain contract + substrate-contracts-node -lruntime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml cargo contract instantiate --suri //Alice --args true -x -y --manifest-path=integration-tests/flipper/Cargo.toml export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) From 8c1d5b32c162b139bd3cb1bc2fe2a4e6ce2ce46e Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:29:10 +0100 Subject: [PATCH 11/26] Fix CI --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af5b910431f..23281550ed4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,7 +490,6 @@ jobs: # run flipper E2E test with on-chain contract substrate-contracts-node -lruntime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml - cargo contract instantiate --suri //Alice --args true -x -y --manifest-path=integration-tests/flipper/Cargo.toml export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored # run the static buffer test with a custom buffer size From ad1a908b50e4c80bba2fc24efc5aacd9308db998 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:29:43 +0100 Subject: [PATCH 12/26] Make `rustfmt` happy --- integration-tests/flipper/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index b2ff2463a0d..103d30589b4 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -120,8 +120,9 @@ pub mod flipper { /// Before executing the test: /// * Make sure you have a node running in the background, /// * Supply the environment variable `CONTRACT_HEX` that points to a deployed - /// flipper contract. You can take the SS58 address which `cargo contract instantiate` - /// gives you and convert it to hex using `subkey inspect `. + /// flipper contract. You can take the SS58 address which + /// `cargo contract instantiate` gives you and convert it to hex using + /// `subkey inspect `. /// /// The test is then run like this: /// From 21d65665542a96268d788b7b45f8aacea18d94ba Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:36:57 +0100 Subject: [PATCH 13/26] Make `rustfmt` happy --- integration-tests/flipper/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index 103d30589b4..83a1a8377ab 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -120,9 +120,9 @@ pub mod flipper { /// Before executing the test: /// * Make sure you have a node running in the background, /// * Supply the environment variable `CONTRACT_HEX` that points to a deployed - /// flipper contract. You can take the SS58 address which - /// `cargo contract instantiate` gives you and convert it to hex using - /// `subkey inspect `. + /// flipper contract. You can take the SS58 address which `cargo contract + /// instantiate` gives you and convert it to hex using `subkey inspect + /// `. /// /// The test is then run like this: /// From 7300c053ec541b79b1af9e89add1da4b51ec742d Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 12:40:11 +0100 Subject: [PATCH 14/26] Revert me: Install `subkey` --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23281550ed4..d4b9e751e73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,6 +490,7 @@ jobs: # run flipper E2E test with on-chain contract substrate-contracts-node -lruntime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml + cargo install subkey export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored # run the static buffer test with a custom buffer size From c9fb742f6ffa6967b18085275b903f6fc19a1db0 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 13:53:22 +0100 Subject: [PATCH 15/26] Revert me: Debug CI --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4b9e751e73..34486dc78cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -488,11 +488,12 @@ jobs: scripts/for_all_contracts_exec.sh --path integration-tests --ignore static-buffer -- cargo test \ --verbose --all-features --manifest-path {} # run flipper E2E test with on-chain contract - substrate-contracts-node -lruntime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & + substrate-contracts-node -lruntime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml cargo install subkey + cargo contract instantiate --salt 10 --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1 export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) - CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored + RUST_LOG=info CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored --nocapture # run the static buffer test with a custom buffer size cargo clean --manifest-path integration-tests/static-buffer/Cargo.toml INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path integration-tests/static-buffer/Cargo.toml --all-features From 376ccd0fcdd56a6a1de364173fb8abfd3f0122ef Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 14:33:52 +0100 Subject: [PATCH 16/26] Fix CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34486dc78cd..9101a05e774 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -493,7 +493,7 @@ jobs: cargo install subkey cargo contract instantiate --salt 10 --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1 export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) - RUST_LOG=info CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored --nocapture + RUST_LOG=info CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract --manifest-path integration-tests/flipper/Cargo.toml -- --ignored --nocapture # run the static buffer test with a custom buffer size cargo clean --manifest-path integration-tests/static-buffer/Cargo.toml INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path integration-tests/static-buffer/Cargo.toml --all-features From b0f52fe9d708ae6946781a0fb849571a251c604e Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 14:56:04 +0100 Subject: [PATCH 17/26] Improve formatting --- .github/workflows/ci.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9101a05e774..ceef7657d29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -487,13 +487,21 @@ jobs: # run all tests with --all-features, which will run the e2e-tests feature if present scripts/for_all_contracts_exec.sh --path integration-tests --ignore static-buffer -- cargo test \ --verbose --all-features --manifest-path {} + # run flipper E2E test with on-chain contract - substrate-contracts-node -lruntime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & + substrate-contracts-node -lruntime::contracts=debug 2>&1 & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml cargo install subkey - cargo contract instantiate --salt 10 --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1 - export CONTRACT_HEX=$(cargo contract instantiate --manifest-path integration-tests/flipper/Cargo.toml --suri //Alice --args true -x -y --output-json | jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) - RUST_LOG=info CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test --features e2e-tests e2e_test_deployed_contract --manifest-path integration-tests/flipper/Cargo.toml -- --ignored --nocapture + export CONTRACT_HEX=$(cargo contract instantiate \ + --manifest-path integration-tests/flipper/Cargo.toml \ + --suri //Alice --args true -x -y --output-json | \ + jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) + CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test \ + --features e2e-tests \ + --manifest-path integration-tests/flipper/Cargo.toml \ + e2e_test_deployed_contract \ + -- --ignored --nocapture + # run the static buffer test with a custom buffer size cargo clean --manifest-path integration-tests/static-buffer/Cargo.toml INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path integration-tests/static-buffer/Cargo.toml --all-features From 68da706162e78795fcf3a074a5a5fb4576984f92 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 15:10:41 +0100 Subject: [PATCH 18/26] Remove local `subkey` installation --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ceef7657d29..5f4e10c3bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -491,7 +491,6 @@ jobs: # run flipper E2E test with on-chain contract substrate-contracts-node -lruntime::contracts=debug 2>&1 & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml - cargo install subkey export CONTRACT_HEX=$(cargo contract instantiate \ --manifest-path integration-tests/flipper/Cargo.toml \ --suri //Alice --args true -x -y --output-json | \ From 19cbdf8e2ef53e310ccb7607bf2669ce3d3fbaa9 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 15:11:10 +0100 Subject: [PATCH 19/26] Revert "Revert me: Run CI with isolated examples" This reverts commit 9900cc92777095d264b5fe5f78b0fa3ebe8640f8. --- .../basic-contract-caller/.gitignore | 9 + .../basic-contract-caller/Cargo.toml | 32 + .../basic-contract-caller/lib.rs | 41 + .../other-contract/.gitignore | 9 + .../other-contract/Cargo.toml | 23 + .../other-contract/lib.rs | 33 + .../call-builder-return-value/Cargo.toml | 27 + .../call-builder-return-value/lib.rs | 298 +++++ integration-tests/call-runtime/.gitignore | 9 + integration-tests/call-runtime/Cargo.toml | 34 + integration-tests/call-runtime/README.md | 29 + integration-tests/call-runtime/lib.rs | 284 +++++ .../combined-extension/.gitignore | 9 + .../combined-extension/Cargo.toml | 26 + .../combined-extension/README.md | 19 + integration-tests/combined-extension/lib.rs | 189 +++ .../conditional-compilation/.gitignore | 9 + .../conditional-compilation/Cargo.toml | 25 + .../conditional-compilation/lib.rs | 161 +++ integration-tests/contract-storage/.gitignore | 9 + integration-tests/contract-storage/Cargo.toml | 23 + .../contract-storage/e2e_tests.rs | 122 ++ integration-tests/contract-storage/lib.rs | 76 ++ .../contract-terminate/.gitignore | 9 + .../contract-terminate/Cargo.toml | 23 + integration-tests/contract-terminate/lib.rs | 97 ++ .../contract-transfer/.gitignore | 9 + .../contract-transfer/Cargo.toml | 23 + integration-tests/contract-transfer/lib.rs | 264 ++++ integration-tests/custom-allocator/.gitignore | 9 + integration-tests/custom-allocator/Cargo.toml | 28 + integration-tests/custom-allocator/lib.rs | 170 +++ .../custom-environment/.gitignore | 9 + .../custom-environment/Cargo.toml | 26 + .../custom-environment/README.md | 28 + integration-tests/custom-environment/lib.rs | 146 +++ integration-tests/dns/.gitignore | 9 + integration-tests/dns/Cargo.toml | 19 + integration-tests/dns/lib.rs | 262 ++++ integration-tests/e2e-call-runtime/.gitignore | 9 + integration-tests/e2e-call-runtime/Cargo.toml | 23 + integration-tests/e2e-call-runtime/lib.rs | 90 ++ .../e2e-runtime-only-backend/.gitignore | 9 + .../e2e-runtime-only-backend/Cargo.toml | 23 + .../e2e-runtime-only-backend/lib.rs | 159 +++ integration-tests/erc1155/.gitignore | 9 + integration-tests/erc1155/Cargo.toml | 19 + integration-tests/erc1155/lib.rs | 805 ++++++++++++ integration-tests/erc20/.gitignore | 9 + integration-tests/erc20/Cargo.toml | 23 + integration-tests/erc20/lib.rs | 643 ++++++++++ integration-tests/erc721/.gitignore | 9 + integration-tests/erc721/Cargo.toml | 19 + integration-tests/erc721/lib.rs | 637 ++++++++++ integration-tests/events/.gitignore | 9 + integration-tests/events/Cargo.toml | 35 + .../events/event-def-unused/Cargo.toml | 13 + .../events/event-def-unused/src/lib.rs | 15 + integration-tests/events/event-def/Cargo.toml | 13 + integration-tests/events/event-def/src/lib.rs | 14 + .../events/event-def2/Cargo.toml | 13 + .../events/event-def2/src/lib.rs | 9 + integration-tests/events/lib.rs | 415 ++++++ integration-tests/incrementer/.gitignore | 9 + integration-tests/incrementer/Cargo.toml | 19 + integration-tests/incrementer/lib.rs | 57 + .../lang-err-integration-tests/.gitignore | 9 + .../call-builder-delegate/Cargo.toml | 27 + .../call-builder-delegate/lib.rs | 185 +++ .../call-builder/Cargo.toml | 29 + .../call-builder/lib.rs | 587 +++++++++ .../constructors-return-value/Cargo.toml | 23 + .../constructors-return-value/lib.rs | 250 ++++ .../contract-ref/Cargo.toml | 26 + .../contract-ref/lib.rs | 188 +++ .../integration-flipper/Cargo.toml | 23 + .../integration-flipper/lib.rs | 152 +++ .../lazyvec-integration-test/.gitignore | 9 + .../lazyvec-integration-test/Cargo.toml | 23 + .../lazyvec-integration-test/lib.rs | 156 +++ .../.cargo/config.toml | 3 + .../mapping-integration-tests/.gitignore | 9 + .../mapping-integration-tests/Cargo.toml | 23 + .../mapping-integration-tests/lib.rs | 418 +++++++ integration-tests/mother/.gitignore | 12 + integration-tests/mother/Cargo.toml | 23 + integration-tests/mother/lib.rs | 251 ++++ .../multi-contract-caller/.gitignore | 9 + .../.images/code-hashes.png | Bin 0 -> 95399 bytes .../multi-contract-caller/Cargo.toml | 38 + .../multi-contract-caller/README.md | 44 + .../accumulator/Cargo.toml | 17 + .../multi-contract-caller/accumulator/lib.rs | 35 + .../multi-contract-caller/adder/Cargo.toml | 22 + .../multi-contract-caller/adder/lib.rs | 32 + .../multi-contract-caller/build-all.sh | 8 + .../multi-contract-caller/lib.rs | 212 ++++ .../multi-contract-caller/subber/Cargo.toml | 22 + .../multi-contract-caller/subber/lib.rs | 32 + integration-tests/multisig/.gitignore | 9 + integration-tests/multisig/Cargo.toml | 19 + integration-tests/multisig/lib.rs | 1112 +++++++++++++++++ integration-tests/payment-channel/.gitignore | 9 + integration-tests/payment-channel/Cargo.toml | 25 + integration-tests/payment-channel/lib.rs | 590 +++++++++ integration-tests/psp22-extension/.gitignore | 9 + integration-tests/psp22-extension/Cargo.toml | 19 + integration-tests/psp22-extension/README.md | 47 + integration-tests/psp22-extension/lib.rs | 265 ++++ .../runtime/psp22-extension-example.rs | 446 +++++++ integration-tests/rand-extension/.gitignore | 9 + integration-tests/rand-extension/Cargo.toml | 19 + integration-tests/rand-extension/README.md | 35 + integration-tests/rand-extension/lib.rs | 160 +++ .../runtime/chain-extension-example.rs | 58 + .../trait-dyn-cross-contract-calls/Cargo.toml | 33 + .../contracts/incrementer/.gitignore | 9 + .../contracts/incrementer/Cargo.toml | 22 + .../contracts/incrementer/lib.rs | 56 + .../trait-dyn-cross-contract-calls/lib.rs | 135 ++ .../traits/.gitignore | 9 + .../traits/Cargo.toml | 19 + .../traits/lib.rs | 16 + integration-tests/trait-erc20/.gitignore | 9 + integration-tests/trait-erc20/Cargo.toml | 19 + integration-tests/trait-erc20/lib.rs | 549 ++++++++ integration-tests/trait-flipper/.gitignore | 9 + integration-tests/trait-flipper/Cargo.toml | 19 + integration-tests/trait-flipper/lib.rs | 66 + .../trait-incrementer/.gitignore | 9 + .../trait-incrementer/Cargo.toml | 22 + integration-tests/trait-incrementer/lib.rs | 70 ++ .../trait-incrementer/traits/.gitignore | 9 + .../trait-incrementer/traits/Cargo.toml | 21 + .../trait-incrementer/traits/lib.rs | 37 + .../upgradeable-contracts/README.md | 36 + .../delegator/.gitignore | 9 + .../delegator/Cargo.toml | 24 + .../delegator/delegatee/.gitignore | 9 + .../delegator/delegatee/Cargo.toml | 20 + .../delegator/delegatee/lib.rs | 43 + .../upgradeable-contracts/delegator/lib.rs | 208 +++ .../set-code-hash/Cargo.toml | 23 + .../set-code-hash/lib.rs | 138 ++ .../updated-incrementer/Cargo.toml | 19 + .../set-code-hash/updated-incrementer/lib.rs | 69 + .../wildcard-selector/.gitignore | 9 + .../wildcard-selector/Cargo.toml | 23 + integration-tests/wildcard-selector/lib.rs | 163 +++ 149 files changed, 13393 insertions(+) create mode 100755 integration-tests/basic-contract-caller/.gitignore create mode 100755 integration-tests/basic-contract-caller/Cargo.toml create mode 100755 integration-tests/basic-contract-caller/lib.rs create mode 100755 integration-tests/basic-contract-caller/other-contract/.gitignore create mode 100755 integration-tests/basic-contract-caller/other-contract/Cargo.toml create mode 100755 integration-tests/basic-contract-caller/other-contract/lib.rs create mode 100755 integration-tests/call-builder-return-value/Cargo.toml create mode 100755 integration-tests/call-builder-return-value/lib.rs create mode 100644 integration-tests/call-runtime/.gitignore create mode 100644 integration-tests/call-runtime/Cargo.toml create mode 100644 integration-tests/call-runtime/README.md create mode 100644 integration-tests/call-runtime/lib.rs create mode 100755 integration-tests/combined-extension/.gitignore create mode 100755 integration-tests/combined-extension/Cargo.toml create mode 100644 integration-tests/combined-extension/README.md create mode 100755 integration-tests/combined-extension/lib.rs create mode 100755 integration-tests/conditional-compilation/.gitignore create mode 100755 integration-tests/conditional-compilation/Cargo.toml create mode 100755 integration-tests/conditional-compilation/lib.rs create mode 100755 integration-tests/contract-storage/.gitignore create mode 100755 integration-tests/contract-storage/Cargo.toml create mode 100644 integration-tests/contract-storage/e2e_tests.rs create mode 100755 integration-tests/contract-storage/lib.rs create mode 100644 integration-tests/contract-terminate/.gitignore create mode 100644 integration-tests/contract-terminate/Cargo.toml create mode 100644 integration-tests/contract-terminate/lib.rs create mode 100644 integration-tests/contract-transfer/.gitignore create mode 100644 integration-tests/contract-transfer/Cargo.toml create mode 100644 integration-tests/contract-transfer/lib.rs create mode 100755 integration-tests/custom-allocator/.gitignore create mode 100755 integration-tests/custom-allocator/Cargo.toml create mode 100755 integration-tests/custom-allocator/lib.rs create mode 100644 integration-tests/custom-environment/.gitignore create mode 100644 integration-tests/custom-environment/Cargo.toml create mode 100644 integration-tests/custom-environment/README.md create mode 100644 integration-tests/custom-environment/lib.rs create mode 100644 integration-tests/dns/.gitignore create mode 100644 integration-tests/dns/Cargo.toml create mode 100644 integration-tests/dns/lib.rs create mode 100644 integration-tests/e2e-call-runtime/.gitignore create mode 100644 integration-tests/e2e-call-runtime/Cargo.toml create mode 100644 integration-tests/e2e-call-runtime/lib.rs create mode 100644 integration-tests/e2e-runtime-only-backend/.gitignore create mode 100644 integration-tests/e2e-runtime-only-backend/Cargo.toml create mode 100644 integration-tests/e2e-runtime-only-backend/lib.rs create mode 100644 integration-tests/erc1155/.gitignore create mode 100644 integration-tests/erc1155/Cargo.toml create mode 100644 integration-tests/erc1155/lib.rs create mode 100644 integration-tests/erc20/.gitignore create mode 100644 integration-tests/erc20/Cargo.toml create mode 100644 integration-tests/erc20/lib.rs create mode 100644 integration-tests/erc721/.gitignore create mode 100644 integration-tests/erc721/Cargo.toml create mode 100644 integration-tests/erc721/lib.rs create mode 100644 integration-tests/events/.gitignore create mode 100644 integration-tests/events/Cargo.toml create mode 100644 integration-tests/events/event-def-unused/Cargo.toml create mode 100644 integration-tests/events/event-def-unused/src/lib.rs create mode 100644 integration-tests/events/event-def/Cargo.toml create mode 100644 integration-tests/events/event-def/src/lib.rs create mode 100644 integration-tests/events/event-def2/Cargo.toml create mode 100644 integration-tests/events/event-def2/src/lib.rs create mode 100644 integration-tests/events/lib.rs create mode 100644 integration-tests/incrementer/.gitignore create mode 100644 integration-tests/incrementer/Cargo.toml create mode 100644 integration-tests/incrementer/lib.rs create mode 100755 integration-tests/lang-err-integration-tests/.gitignore create mode 100755 integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml create mode 100755 integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs create mode 100755 integration-tests/lang-err-integration-tests/call-builder/Cargo.toml create mode 100755 integration-tests/lang-err-integration-tests/call-builder/lib.rs create mode 100644 integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml create mode 100644 integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs create mode 100755 integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml create mode 100755 integration-tests/lang-err-integration-tests/contract-ref/lib.rs create mode 100644 integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml create mode 100644 integration-tests/lang-err-integration-tests/integration-flipper/lib.rs create mode 100755 integration-tests/lazyvec-integration-test/.gitignore create mode 100755 integration-tests/lazyvec-integration-test/Cargo.toml create mode 100755 integration-tests/lazyvec-integration-test/lib.rs create mode 100644 integration-tests/mapping-integration-tests/.cargo/config.toml create mode 100755 integration-tests/mapping-integration-tests/.gitignore create mode 100755 integration-tests/mapping-integration-tests/Cargo.toml create mode 100755 integration-tests/mapping-integration-tests/lib.rs create mode 100755 integration-tests/mother/.gitignore create mode 100755 integration-tests/mother/Cargo.toml create mode 100755 integration-tests/mother/lib.rs create mode 100644 integration-tests/multi-contract-caller/.gitignore create mode 100644 integration-tests/multi-contract-caller/.images/code-hashes.png create mode 100644 integration-tests/multi-contract-caller/Cargo.toml create mode 100644 integration-tests/multi-contract-caller/README.md create mode 100644 integration-tests/multi-contract-caller/accumulator/Cargo.toml create mode 100644 integration-tests/multi-contract-caller/accumulator/lib.rs create mode 100644 integration-tests/multi-contract-caller/adder/Cargo.toml create mode 100644 integration-tests/multi-contract-caller/adder/lib.rs create mode 100755 integration-tests/multi-contract-caller/build-all.sh create mode 100644 integration-tests/multi-contract-caller/lib.rs create mode 100644 integration-tests/multi-contract-caller/subber/Cargo.toml create mode 100644 integration-tests/multi-contract-caller/subber/lib.rs create mode 100755 integration-tests/multisig/.gitignore create mode 100755 integration-tests/multisig/Cargo.toml create mode 100755 integration-tests/multisig/lib.rs create mode 100755 integration-tests/payment-channel/.gitignore create mode 100755 integration-tests/payment-channel/Cargo.toml create mode 100755 integration-tests/payment-channel/lib.rs create mode 100755 integration-tests/psp22-extension/.gitignore create mode 100755 integration-tests/psp22-extension/Cargo.toml create mode 100644 integration-tests/psp22-extension/README.md create mode 100755 integration-tests/psp22-extension/lib.rs create mode 100644 integration-tests/psp22-extension/runtime/psp22-extension-example.rs create mode 100755 integration-tests/rand-extension/.gitignore create mode 100755 integration-tests/rand-extension/Cargo.toml create mode 100644 integration-tests/rand-extension/README.md create mode 100755 integration-tests/rand-extension/lib.rs create mode 100644 integration-tests/rand-extension/runtime/chain-extension-example.rs create mode 100644 integration-tests/trait-dyn-cross-contract-calls/Cargo.toml create mode 100644 integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore create mode 100644 integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml create mode 100644 integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs create mode 100644 integration-tests/trait-dyn-cross-contract-calls/lib.rs create mode 100644 integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore create mode 100644 integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml create mode 100644 integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs create mode 100644 integration-tests/trait-erc20/.gitignore create mode 100644 integration-tests/trait-erc20/Cargo.toml create mode 100644 integration-tests/trait-erc20/lib.rs create mode 100644 integration-tests/trait-flipper/.gitignore create mode 100644 integration-tests/trait-flipper/Cargo.toml create mode 100644 integration-tests/trait-flipper/lib.rs create mode 100644 integration-tests/trait-incrementer/.gitignore create mode 100644 integration-tests/trait-incrementer/Cargo.toml create mode 100644 integration-tests/trait-incrementer/lib.rs create mode 100644 integration-tests/trait-incrementer/traits/.gitignore create mode 100644 integration-tests/trait-incrementer/traits/Cargo.toml create mode 100644 integration-tests/trait-incrementer/traits/lib.rs create mode 100644 integration-tests/upgradeable-contracts/README.md create mode 100644 integration-tests/upgradeable-contracts/delegator/.gitignore create mode 100644 integration-tests/upgradeable-contracts/delegator/Cargo.toml create mode 100644 integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore create mode 100644 integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml create mode 100644 integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs create mode 100644 integration-tests/upgradeable-contracts/delegator/lib.rs create mode 100644 integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml create mode 100644 integration-tests/upgradeable-contracts/set-code-hash/lib.rs create mode 100644 integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml create mode 100644 integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs create mode 100644 integration-tests/wildcard-selector/.gitignore create mode 100644 integration-tests/wildcard-selector/Cargo.toml create mode 100644 integration-tests/wildcard-selector/lib.rs diff --git a/integration-tests/basic-contract-caller/.gitignore b/integration-tests/basic-contract-caller/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/basic-contract-caller/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/basic-contract-caller/Cargo.toml b/integration-tests/basic-contract-caller/Cargo.toml new file mode 100755 index 00000000000..0781400a2f4 --- /dev/null +++ b/integration-tests/basic-contract-caller/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "basic-contract-caller" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +# Note: We **need** to specify the `ink-as-dependency` feature. +# +# If we don't we will end up with linking errors! +other-contract = { path = "other-contract", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + # Note: The metadata generation step requires `std`. If we don't specify this the metadata + # generation for our contract will fail! + "other-contract/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/basic-contract-caller/lib.rs b/integration-tests/basic-contract-caller/lib.rs new file mode 100755 index 00000000000..f988caabf41 --- /dev/null +++ b/integration-tests/basic-contract-caller/lib.rs @@ -0,0 +1,41 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod basic_contract_caller { + /// We import the generated `ContractRef` of our other contract. + /// + /// Note that the other contract must have re-exported it (`pub use + /// OtherContractRef`) for us to have access to it. + use other_contract::OtherContractRef; + + #[ink(storage)] + pub struct BasicContractCaller { + /// We specify that our contract will store a reference to the `OtherContract`. + other_contract: OtherContractRef, + } + + impl BasicContractCaller { + /// In order to use the `OtherContract` we first need to **instantiate** it. + /// + /// To do this we will use the uploaded `code_hash` of `OtherContract`. + #[ink(constructor)] + pub fn new(other_contract_code_hash: Hash) -> Self { + let other_contract = OtherContractRef::new(true) + .code_hash(other_contract_code_hash) + .endowment(0) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate(); + + Self { other_contract } + } + + /// Using the `ContractRef` we can call all the messages of the `OtherContract` as + /// if they were normal Rust methods (because at the end of the day, they + /// are!). + #[ink(message)] + pub fn flip_and_get(&mut self) -> bool { + self.other_contract.flip(); + self.other_contract.get() + } + } +} diff --git a/integration-tests/basic-contract-caller/other-contract/.gitignore b/integration-tests/basic-contract-caller/other-contract/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/basic-contract-caller/other-contract/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/basic-contract-caller/other-contract/Cargo.toml b/integration-tests/basic-contract-caller/other-contract/Cargo.toml new file mode 100755 index 00000000000..c0b4748e22b --- /dev/null +++ b/integration-tests/basic-contract-caller/other-contract/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "other-contract" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/basic-contract-caller/other-contract/lib.rs b/integration-tests/basic-contract-caller/other-contract/lib.rs new file mode 100755 index 00000000000..53e51019476 --- /dev/null +++ b/integration-tests/basic-contract-caller/other-contract/lib.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +/// Re-export the `ContractRef` generated by the ink! codegen. +/// +/// This let's other crates which pull this contract in as a dependency to interact +/// with this contract in a type-safe way. +pub use self::other_contract::OtherContractRef; + +#[ink::contract] +mod other_contract { + + #[ink(storage)] + pub struct OtherContract { + value: bool, + } + + impl OtherContract { + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + } +} diff --git a/integration-tests/call-builder-return-value/Cargo.toml b/integration-tests/call-builder-return-value/Cargo.toml new file mode 100755 index 00000000000..b4723f34a92 --- /dev/null +++ b/integration-tests/call-builder-return-value/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "call_builder_return_value" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +incrementer = { path = "../incrementer", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "incrementer/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs new file mode 100755 index 00000000000..0f0e3d27b7a --- /dev/null +++ b/integration-tests/call-builder-return-value/lib.rs @@ -0,0 +1,298 @@ +//! This contract is used to ensure that the values returned by cross contract calls using +//! the [`CallBuilder`](`ink::env::call::CallBuilder`) are properly decoded. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod call_builder { + use ink::{ + env::{ + call::{ + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }, + prelude::{ + format, + string::{ + String, + ToString, + }, + }, + }; + + #[ink(storage)] + #[derive(Default)] + pub struct CallBuilderReturnValue { + /// Since we're going to `DelegateCall` into the `incrementer` contract, we need + /// to make sure our storage layout matches. + value: i32, + } + + impl CallBuilderReturnValue { + #[ink(constructor)] + pub fn new(value: i32) -> Self { + Self { value } + } + + /// Delegate a call to the given contract/selector and return the result. + #[ink(message)] + pub fn delegate_call(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { + use ink::env::call::build_call; + + build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .invoke() + } + + /// Delegate call to the given contract/selector and attempt to decode the return + /// value into an `i8`. + #[ink(message)] + pub fn delegate_call_short_return_type( + &mut self, + code_hash: Hash, + selector: [u8; 4], + ) -> Result { + use ink::env::call::build_call; + + let result = build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke(); + + match result { + Ok(Ok(value)) => Ok(value), + Ok(Err(err)) => Err(format!("LangError: {:?}", err)), + Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), + Err(err) => Err(format!("Env Error: {:?}", err)), + } + } + + /// Forward a call to the given contract/selector and return the result. + #[ink(message)] + pub fn forward_call(&mut self, address: AccountId, selector: [u8; 4]) -> i32 { + use ink::env::call::build_call; + + build_call::() + .call(address) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .invoke() + } + + /// Forward call to the given contract/selector and attempt to decode the return + /// value into an `i8`. + #[ink(message)] + pub fn forward_call_short_return_type( + &mut self, + address: AccountId, + selector: [u8; 4], + ) -> Result { + use ink::env::call::build_call; + + let result = build_call::() + .call(address) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke(); + + match result { + Ok(Ok(value)) => Ok(value), + Ok(Err(err)) => Err(format!("LangError: {:?}", err)), + Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), + Err(err) => Err(format!("Env Error: {:?}", err)), + } + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use incrementer::IncrementerRef; + use ink_e2e::{ + ChainBackend, + ContractsBackend, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_delegate_call_return_value_returns_correct_value< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let expected_value = 42; + let mut constructor = CallBuilderReturnValueRef::new(expected_value); + let call_builder = client + .instantiate("call_builder_return_value", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("incrementer", &origin) + .submit() + .await + .expect("upload `incrementer` failed") + .code_hash; + + let selector = ink::selector_bytes!("get"); + let call = call_builder_call.delegate_call(code_hash, selector); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Client failed to call `call_builder::invoke`.") + .return_value(); + + assert_eq!( + call_result, expected_value, + "Decoded an unexpected value from the call." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_delegate_call_return_value_errors_if_return_data_too_long< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderReturnValueRef::new(42); + let call_builder = client + .instantiate("call_builder_return_value", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("incrementer", &origin) + .submit() + .await + .expect("upload `incrementer` failed") + .code_hash; + + let selector = ink::selector_bytes!("get"); + let call = + call_builder_call.delegate_call_short_return_type(code_hash, selector); + let call_result: Result = + client.call(&origin, &call).dry_run().await?.return_value(); + + assert!( + call_result.is_err(), + "Should fail of decoding an `i32` into an `i8`" + ); + assert_eq!( + "Decode Error".to_string(), + call_result.unwrap_err(), + "Should fail to decode short type if bytes remain from return data." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_forward_call_return_value_returns_correct_value< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderReturnValueRef::new(0); + let call_builder = client + .instantiate("call_builder_return_value", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let expected_value = 42; + let mut incrementer_constructor = IncrementerRef::new(expected_value); + let incrementer = client + .instantiate("incrementer", &origin, &mut incrementer_constructor) + .submit() + .await + .expect("instantiate failed"); + + let selector = ink::selector_bytes!("get"); + let call = call_builder_call.forward_call(incrementer.account_id, selector); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Client failed to call `call_builder::invoke`.") + .return_value(); + + assert_eq!( + call_result, expected_value, + "Decoded an unexpected value from the call." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_forward_call_return_value_errors_if_return_data_too_long< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderReturnValueRef::new(0); + let call_builder = client + .instantiate("call_builder_return_value", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let expected_value = 42; + let mut incrementer_constructor = IncrementerRef::new(expected_value); + let incrementer = client + .instantiate("incrementer", &origin, &mut incrementer_constructor) + .submit() + .await + .expect("instantiate failed"); + + let selector = ink::selector_bytes!("get"); + let call = call_builder_call + .forward_call_short_return_type(incrementer.account_id, selector); + let call_result: Result = + client.call(&origin, &call).dry_run().await?.return_value(); + + assert!( + call_result.is_err(), + "Should fail of decoding an `i32` into an `i8`" + ); + assert_eq!( + "Decode Error".to_string(), + call_result.unwrap_err(), + "Should fail to decode short type if bytes remain from return data." + ); + + Ok(()) + } + } +} diff --git a/integration-tests/call-runtime/.gitignore b/integration-tests/call-runtime/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/call-runtime/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/call-runtime/Cargo.toml b/integration-tests/call-runtime/Cargo.toml new file mode 100644 index 00000000000..1876308e0fe --- /dev/null +++ b/integration-tests/call-runtime/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "call-runtime" +version = "4.0.0" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +# Substrate +# +# We need to explicitly turn off some of the `sp-io` features, to avoid conflicts +# (especially for global allocator). +# +# See also: https://substrate.stackexchange.com/questions/4733/error-when-compiling-a-contract-using-the-xcm-chain-extension. +sp-io = { version = "23.0.0", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] } +sp-runtime = { version = "24.0.0", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "sp-runtime/std", + "sp-io/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/call-runtime/README.md b/integration-tests/call-runtime/README.md new file mode 100644 index 00000000000..acf3c726f21 --- /dev/null +++ b/integration-tests/call-runtime/README.md @@ -0,0 +1,29 @@ +# `call-runtime` example + +## What is this example about? + +It demonstrates how to call a runtime dispatchable from an ink! contract. + +## Chain-side configuration + +To integrate this example into Substrate you need to adjust pallet contracts configuration in your runtime: + ```rust + // In your node's runtime configuration file (runtime.rs) + impl pallet_contracts::Config for Runtime { + … + // `Everything` or anything that will allow for the `Balances::transfer` extrinsic. + type CallFilter = frame_support::traits::Everything; + type UnsafeUnstableInterface = ConstBool; + … + } + ``` + +## Comparison to `ChainExtension` + +Just as a chain extension, `call_runtime` API allows contracts for direct calling to the runtime. +You can trigger any extrinsic that is not forbidden by `pallet_contracts::Config::CallFilter`. +Consider writing a chain extension if you need to perform one of the following tasks: +- Return data. +- Provide functionality **exclusively** to contracts. +- Provide custom weights. +- Avoid the need to keep the `Call` data structure stable. diff --git a/integration-tests/call-runtime/lib.rs b/integration-tests/call-runtime/lib.rs new file mode 100644 index 00000000000..0fa1830003f --- /dev/null +++ b/integration-tests/call-runtime/lib.rs @@ -0,0 +1,284 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::primitives::AccountId; +use sp_runtime::MultiAddress; + +/// A part of the runtime dispatchable API. +/// +/// For now, `ink!` doesn't provide any support for exposing the real `RuntimeCall` enum, +/// which fully describes the composed API of all the pallets present in runtime. Hence, +/// in order to use `call-runtime` functionality, we have to provide at least a partial +/// object, which correctly encodes the target extrinsic. +/// +/// You can investigate the full `RuntimeCall` definition by either expanding +/// `construct_runtime!` macro application or by using secondary tools for reading chain +/// metadata, like `subxt`. +#[ink::scale_derive(Encode)] +enum RuntimeCall { + /// This index can be found by investigating runtime configuration. You can check the + /// pallet order inside `construct_runtime!` block and read the position of your + /// pallet (0-based). + /// + /// + /// [See here for more.](https://substrate.stackexchange.com/questions/778/how-to-get-pallet-index-u8-of-a-pallet-in-runtime) + #[codec(index = 4)] + Balances(BalancesCall), +} + +#[ink::scale_derive(Encode)] +enum BalancesCall { + /// This index can be found by investigating the pallet dispatchable API. In your + /// pallet code, look for `#[pallet::call]` section and check + /// `#[pallet::call_index(x)]` attribute of the call. If these attributes are + /// missing, use source-code order (0-based). + #[codec(index = 0)] + Transfer { + dest: MultiAddress, + #[codec(compact)] + value: u128, + }, +} + +#[ink::contract] +mod runtime_call { + use crate::{ + BalancesCall, + RuntimeCall, + }; + + use ink::env::Error as EnvError; + + /// A trivial contract with a single message, that uses `call-runtime` API for + /// performing native token transfer. + #[ink(storage)] + #[derive(Default)] + pub struct RuntimeCaller; + + #[derive(Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum RuntimeError { + CallRuntimeFailed, + } + + impl From for RuntimeError { + fn from(e: EnvError) -> Self { + use ink::env::ReturnErrorCode; + match e { + EnvError::ReturnError(ReturnErrorCode::CallRuntimeFailed) => { + RuntimeError::CallRuntimeFailed + } + _ => panic!("Unexpected error from `pallet-contracts`."), + } + } + } + + impl RuntimeCaller { + /// The constructor is `payable`, so that during instantiation it can be given + /// some tokens that will be further transferred with + /// `transfer_through_runtime` message. + #[ink(constructor, payable)] + pub fn new() -> Self { + Default::default() + } + + /// Tries to transfer `value` from the contract's balance to `receiver`. + /// + /// Fails if: + /// - called in the off-chain environment + /// - the chain forbids contracts to call `Balances::transfer` (`CallFilter` is + /// too restrictive) + /// - after the transfer, `receiver` doesn't have at least existential deposit + /// - the contract doesn't have enough balance + #[ink(message)] + pub fn transfer_through_runtime( + &mut self, + receiver: AccountId, + value: Balance, + ) -> Result<(), RuntimeError> { + self.env() + .call_runtime(&RuntimeCall::Balances(BalancesCall::Transfer { + dest: receiver.into(), + value, + })) + .map_err(Into::into) + } + + /// Tries to trigger `call_runtime` API with rubbish data. + /// + /// # Note + /// + /// This message is for testing purposes only. + #[ink(message)] + pub fn call_nonexistent_extrinsic(&mut self) -> Result<(), RuntimeError> { + self.env().call_runtime(&()).map_err(Into::into) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + ChainBackend, + ContractsBackend, + }; + + use ink::{ + env::{ + test::default_accounts, + DefaultEnvironment, + }, + primitives::AccountId, + }; + + type E2EResult = Result>; + + /// The base number of indivisible units for balances on the + /// `substrate-contracts-node`. + const UNIT: Balance = 1_000_000_000_000; + + /// The contract will be given 1000 tokens during instantiation. + const CONTRACT_BALANCE: Balance = 1_000 * UNIT; + + /// The receiver will get enough funds to have the required existential deposit. + /// + /// If your chain has this threshold higher, increase the transfer value. + const TRANSFER_VALUE: Balance = 1 / 10 * UNIT; + + /// An amount that is below the existential deposit, so that a transfer to an + /// empty account fails. + /// + /// Must not be zero, because such an operation would be a successful no-op. + const INSUFFICIENT_TRANSFER_VALUE: Balance = 1; + + /// Positive case scenario: + /// - the call is valid + /// - the call execution succeeds + #[ink_e2e::test] + async fn transfer_with_call_runtime_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = RuntimeCallerRef::new(); + let contract = client + .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) + .value(CONTRACT_BALANCE) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let receiver: AccountId = default_accounts::().bob; + + let contract_balance_before = client + .free_balance(contract.account_id) + .await + .expect("Failed to get account balance"); + let receiver_balance_before = client + .free_balance(receiver) + .await + .expect("Failed to get account balance"); + + // when + let transfer_message = + call.transfer_through_runtime(receiver, TRANSFER_VALUE); + + let call_res = client + .call(&ink_e2e::alice(), &transfer_message) + .submit() + .await + .expect("call failed"); + + assert!(call_res.return_value().is_ok()); + + // then + let contract_balance_after = client + .free_balance(contract.account_id) + .await + .expect("Failed to get account balance"); + let receiver_balance_after = client + .free_balance(receiver) + .await + .expect("Failed to get account balance"); + + assert_eq!( + contract_balance_before, + contract_balance_after + TRANSFER_VALUE + ); + assert_eq!( + receiver_balance_before, + receiver_balance_after - TRANSFER_VALUE + ); + + Ok(()) + } + + /// Negative case scenario: + /// - the call is valid + /// - the call execution fails + #[ink_e2e::test] + async fn transfer_with_call_runtime_fails_when_execution_fails< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = RuntimeCallerRef::new(); + let contract = client + .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) + .value(CONTRACT_BALANCE) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let receiver: AccountId = default_accounts::().bob; + + // when + let transfer_message = + call.transfer_through_runtime(receiver, INSUFFICIENT_TRANSFER_VALUE); + + let call_res = client + .call(&ink_e2e::alice(), &transfer_message) + .dry_run() + .await? + .return_value(); + + // then + assert!(matches!(call_res, Err(RuntimeError::CallRuntimeFailed))); + + Ok(()) + } + + /// Negative case scenario: + /// - the call is invalid + #[ink_e2e::test] + async fn transfer_with_call_runtime_fails_when_call_is_invalid< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = RuntimeCallerRef::new(); + let contract = client + .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) + .value(CONTRACT_BALANCE) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let transfer_message = call.call_nonexistent_extrinsic(); + + let call_res = client + .call(&ink_e2e::alice(), &transfer_message) + .dry_run() + .await; + + // then + assert!(call_res.is_err()); + + Ok(()) + } + } +} diff --git a/integration-tests/combined-extension/.gitignore b/integration-tests/combined-extension/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/combined-extension/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/combined-extension/Cargo.toml b/integration-tests/combined-extension/Cargo.toml new file mode 100755 index 00000000000..e93251606a0 --- /dev/null +++ b/integration-tests/combined-extension/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "combined_extension" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } +psp22_extension = { path = "../psp22-extension", default-features = false, features = ["ink-as-dependency"] } +rand_extension = { path = "../rand-extension", default-features = false, features = ["ink-as-dependency"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "psp22_extension/std", + "rand_extension/std", +] +ink-as-dependency = [ + "psp22_extension/ink-as-dependency", + "rand_extension/ink-as-dependency", +] diff --git a/integration-tests/combined-extension/README.md b/integration-tests/combined-extension/README.md new file mode 100644 index 00000000000..f71d8cbaeaa --- /dev/null +++ b/integration-tests/combined-extension/README.md @@ -0,0 +1,19 @@ +# Combined Chain Extension Example + +## What is this example about? + +It demonstrates how to combine several chain extensions and call them from ink!. + +See [this chapter](https://use.ink/macros-attributes/chain-extension) +in our ink! documentation for more details about chain extensions. + + +This example uses two chain extensions, `FetchRandom`(from [rand-extension](../rand-extension)) +and `Psp22Extension`(from [psp22-extension](../psp22-extension)) defined in other examples. +The example shows how to combine two chain extensions together +and use them in the contract along with each other. +Also example shows how to mock each chain extension for testing. + +This example doesn't show how to define a chain extension and how +to implement in on the runtime side. For that purpose, you can +check the two examples mentioned above. diff --git a/integration-tests/combined-extension/lib.rs b/integration-tests/combined-extension/lib.rs new file mode 100755 index 00000000000..76824771354 --- /dev/null +++ b/integration-tests/combined-extension/lib.rs @@ -0,0 +1,189 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::env::{ + DefaultEnvironment, + Environment, +}; +use psp22_extension::Psp22Extension; +use rand_extension::{ + FetchRandom, + RandomReadErr, +}; + +ink::combine_extensions! { + /// This extension combines the [`FetchRandom`] and [`Psp22Extension`] extensions. + /// It is possible to combine any number of extensions in this way. + /// + /// This structure is an instance that is returned by the `self.env().extension()` call. + pub struct CombinedChainExtension { + /// The instance of the [`Psp22Extension`] chain extension. + /// + /// It provides you access to `PSP22` functionality. + pub psp22: Psp22Extension, + /// The instance of the [`FetchRandom`] chain extension. + /// + /// It provides you access to randomness functionality. + pub rand: FetchRandom, + } +} + +/// An environment using default ink environment types, with PSP-22 extension included +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(TypeInfo)] +pub enum CustomEnvironment {} + +/// We use the same types and values as for [`DefaultEnvironment`] except the +/// [`Environment::ChainExtension`] type. +impl Environment for CustomEnvironment { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type Timestamp = ::Timestamp; + type BlockNumber = ::BlockNumber; + + /// Setting up the combined chain extension as a primary extension. + /// + /// The `self.env().extension()` call returns the [`CombinedInstance`] + /// that provides access two both chain extensions. + type ChainExtension = CombinedChainExtension; +} + +#[ink::contract(env = crate::CustomEnvironment)] +mod combined_extension { + use super::*; + use psp22_extension::Psp22Error; + + /// Defines the storage of our contract. + /// + /// The example shows how to call each extension and test it, + /// so we don't need any state to save. + #[ink(storage)] + #[derive(Default)] + pub struct CombinedExtensionContract; + + impl CombinedExtensionContract { + /// Constructor that initializes empty storage. + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Returns the random value from extension. + #[ink(message)] + pub fn get_rand(&self) -> Result<[u8; 32], RandomReadErr> { + self.env().extension().rand.fetch_random([0; 32] /* seed */) + } + + /// Returns the total supply from PSP22 extension. + #[ink(message)] + pub fn get_total_supply(&self) -> Result { + self.env().extension().psp22.total_supply(0) + } + } + + /// Unit tests in Rust are normally defined within such a `#[cfg(test)]` + #[cfg(test)] + mod tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + + const RANDOM_VALUE: [u8; 32] = [3; 32]; + + /// Mocking the random extension to return results that we want in the tests. + struct MockedRandExtension; + impl ink::env::test::ChainExtension for MockedRandExtension { + fn ext_id(&self) -> u16 { + // It is identifier used by [`rand_extension::FetchRandom`] extension. + 666 + } + + fn call( + &mut self, + _func_id: u16, + _input: &[u8], + output: &mut Vec, + ) -> u32 { + ink::scale::Encode::encode_to(&RANDOM_VALUE, output); + 0 + } + } + + #[ink::test] + fn rand_chain_extension_works() { + let contract = CombinedExtensionContract::new(); + + // given + let result = std::panic::catch_unwind(|| contract.get_rand()); + // The call to random extension should fail because it is not registered. + assert!(result.is_err()); + + // when + ink::env::test::register_chain_extension(MockedRandExtension); + + // then + assert_eq!(contract.get_rand(), Ok(RANDOM_VALUE)); + } + + const TOTAL_SUPPLY: u128 = 1377; + + /// Mocking the PSP22 extension to return results that we want in the tests. + /// + /// Because this extension has many methods, we want to implement only one of + /// them: + /// - `total_supply` with corresponding `func_id` - `0x162d`. + struct MockedPSP22Extension; + impl ink::env::test::ChainExtension for MockedPSP22Extension { + fn ext_id(&self) -> u16 { + // It is identifier used by [`psp22_extension::Psp22Extension`] extension. + 13 + } + + fn call(&mut self, func_id: u16, _input: &[u8], output: &mut Vec) -> u32 { + match func_id { + 0x162d /* `func_id` of the `total_supply` function */ => { + ink::scale::Encode::encode_to(&TOTAL_SUPPLY, output); + 0 + } + _ => { + 1 + } + } + } + } + + #[ink::test] + fn psp22_chain_extension_works() { + let contract = CombinedExtensionContract::new(); + + // given + let result = std::panic::catch_unwind(|| contract.get_total_supply()); + // The call to PSP22 extension should fail because it is not registered. + assert!(result.is_err()); + + // when + ink::env::test::register_chain_extension(MockedPSP22Extension); + + // then + assert_eq!(contract.get_total_supply(), Ok(TOTAL_SUPPLY)); + } + + #[ink::test] + fn both_chain_extensions_work() { + let contract = CombinedExtensionContract::new(); + + // given + assert!(std::panic::catch_unwind(|| contract.get_rand()).is_err()); + assert!(std::panic::catch_unwind(|| { contract.get_total_supply() }).is_err()); + + // when + ink::env::test::register_chain_extension(MockedRandExtension); + ink::env::test::register_chain_extension(MockedPSP22Extension); + + // then + assert_eq!(contract.get_rand(), Ok(RANDOM_VALUE)); + assert_eq!(contract.get_total_supply(), Ok(TOTAL_SUPPLY)); + } + } +} diff --git a/integration-tests/conditional-compilation/.gitignore b/integration-tests/conditional-compilation/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/conditional-compilation/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/conditional-compilation/Cargo.toml b/integration-tests/conditional-compilation/Cargo.toml new file mode 100755 index 00000000000..2c2b7c3566d --- /dev/null +++ b/integration-tests/conditional-compilation/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "conditional-compilation" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] +# user-defined features +foo = [] +bar = [] diff --git a/integration-tests/conditional-compilation/lib.rs b/integration-tests/conditional-compilation/lib.rs new file mode 100755 index 00000000000..8619493b9d4 --- /dev/null +++ b/integration-tests/conditional-compilation/lib.rs @@ -0,0 +1,161 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::trait_definition] +pub trait Flip { + /// Flips the current value of the Flipper's boolean. + #[ink(message)] + fn flip(&mut self); + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + fn get(&self) -> bool; + + #[cfg(feature = "foo")] + #[ink(message)] + fn push_foo(&mut self, value: bool); +} + +#[ink::contract] +pub mod conditional_compilation { + use super::Flip; + + /// Feature gated event + #[cfg(feature = "foo")] + #[ink(event)] + pub struct Changes { + // attributing event field with `cfg` is not allowed + new_value: bool, + #[ink(topic)] + by: AccountId, + } + + /// Feature gated event + #[cfg(feature = "bar")] + #[ink(event)] + pub struct ChangesDated { + // attributing event field with `cfg` is not allowed + new_value: bool, + #[ink(topic)] + by: AccountId, + when: BlockNumber, + } + + #[ink(storage)] + pub struct ConditionalCompilation { + value: bool, + } + + impl ConditionalCompilation { + /// Creates a new flipper smart contract initialized to `false`. + #[ink(constructor)] + pub fn new() -> Self { + Self { + value: Default::default(), + } + } + + /// Constructor that is included when `foo` is enabled + #[cfg(feature = "foo")] + #[ink(constructor)] + pub fn new_foo(value: bool) -> Self { + Self { value } + } + + /// Constructor that is included when `bar` is enabled + #[cfg(feature = "bar")] + #[ink(constructor)] + pub fn new_bar(value: bool) -> Self { + Self { value } + } + + /// Constructor that is included with either `foo` or `bar` features enabled + #[cfg(feature = "foo")] + #[cfg(feature = "bar")] + #[ink(constructor)] + pub fn new_foo_bar(value: bool) -> Self { + Self { value } + } + + #[cfg(feature = "foo")] + #[ink(message)] + pub fn inherent_flip_foo(&mut self) { + self.value = !self.value; + let caller = Self::env().caller(); + Self::env().emit_event(Changes { + new_value: self.value, + by: caller, + }); + } + + #[cfg(feature = "bar")] + #[ink(message)] + pub fn inherent_flip_bar(&mut self) { + let caller = Self::env().caller(); + let block_number = Self::env().block_number(); + self.value = !self.value; + Self::env().emit_event(ChangesDated { + new_value: self.value, + by: caller, + when: block_number, + }); + } + } + + impl Flip for ConditionalCompilation { + #[ink(message)] + fn flip(&mut self) { + self.value = !self.value; + } + + #[ink(message)] + fn get(&self) -> bool { + self.value + } + + /// Feature gated mutating message + #[cfg(feature = "foo")] + #[ink(message)] + fn push_foo(&mut self, value: bool) { + let caller = Self::env().caller(); + Self::env().emit_event(Changes { + new_value: value, + by: caller, + }); + self.value = value; + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + let flipper = ConditionalCompilation::new(); + assert!(!flipper.get()); + } + + #[ink::test] + fn it_works() { + let mut flipper = ConditionalCompilation::new(); + // Can call using universal call syntax using the trait. + assert!(!::get(&flipper)); + ::flip(&mut flipper); + // Normal call syntax possible to as long as the trait is in scope. + assert!(flipper.get()); + } + + #[cfg(feature = "foo")] + #[ink::test] + fn foo_works() { + let mut flipper = ConditionalCompilation::new_foo(false); + + flipper.inherent_flip_foo(); + assert!(flipper.get()); + + ::push_foo(&mut flipper, false); + assert!(!flipper.get()) + } + } +} diff --git a/integration-tests/contract-storage/.gitignore b/integration-tests/contract-storage/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/contract-storage/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/contract-storage/Cargo.toml b/integration-tests/contract-storage/Cargo.toml new file mode 100755 index 00000000000..efbbd475132 --- /dev/null +++ b/integration-tests/contract-storage/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "contract-storage" +version = "4.2.0" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/contract-storage/e2e_tests.rs b/integration-tests/contract-storage/e2e_tests.rs new file mode 100644 index 00000000000..d5655139bd8 --- /dev/null +++ b/integration-tests/contract-storage/e2e_tests.rs @@ -0,0 +1,122 @@ +use super::contract_storage::*; +use ink_e2e::ContractsBackend; + +type E2EResult = std::result::Result>; + +#[ink_e2e::test] +async fn get_contract_storage_consumes_entire_buffer( + mut client: Client, +) -> E2EResult<()> { + // given + let mut constructor = ContractStorageRef::new(); + let contract = client + .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call.set_and_get_storage_all_data_consumed(), + ) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + assert!(result.is_ok()); + + Ok(()) +} + +#[ink_e2e::test] +async fn get_contract_storage_fails_when_extra_data( + mut client: Client, +) -> E2EResult<()> { + // given + let mut constructor = ContractStorageRef::new(); + let contract = client + .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call.set_and_get_storage_partial_data_consumed(), + ) + .submit() + .await; + + assert!( + result.is_err(), + "Expected the contract to revert when only partially consuming the buffer" + ); + + Ok(()) +} + +#[ink_e2e::test] +async fn take_contract_storage_consumes_entire_buffer( + mut client: Client, +) -> E2EResult<()> { + // given + let mut constructor = ContractStorageRef::new(); + let contract = client + .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call.set_and_take_storage_all_data_consumed(), + ) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + assert!(result.is_ok()); + + Ok(()) +} + +#[ink_e2e::test] +async fn take_contract_storage_fails_when_extra_data( + mut client: Client, +) -> E2EResult<()> { + // given + let mut constructor = ContractStorageRef::new(); + let contract = client + .instantiate("contract-storage", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call.set_and_take_storage_partial_data_consumed(), + ) + .submit() + .await; + + assert!( + result.is_err(), + "Expected the contract to revert when only partially consuming the buffer" + ); + + Ok(()) +} diff --git a/integration-tests/contract-storage/lib.rs b/integration-tests/contract-storage/lib.rs new file mode 100755 index 00000000000..1404711f13c --- /dev/null +++ b/integration-tests/contract-storage/lib.rs @@ -0,0 +1,76 @@ +//! A smart contract to test reading and writing contract storage + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod contract_storage { + use ink::prelude::{ + format, + string::String, + }; + + /// A contract for testing reading and writing contract storage. + #[ink(storage)] + #[derive(Default)] + pub struct ContractStorage; + + impl ContractStorage { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Read from the contract storage slot, consuming all the data from the buffer. + #[ink(message)] + pub fn set_and_get_storage_all_data_consumed(&self) -> Result<(), String> { + let key = 0u32; + let value = [0x42; 32]; + ink::env::set_contract_storage(&key, &value); + let loaded_value = ink::env::get_contract_storage(&key) + .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; + assert_eq!(loaded_value, Some(value)); + Ok(()) + } + + /// Read from the contract storage slot, only partially consuming data from the + /// buffer. + #[ink(message)] + pub fn set_and_get_storage_partial_data_consumed(&self) -> Result<(), String> { + let key = 0u32; + let value = [0x42; 32]; + ink::env::set_contract_storage(&key, &value); + // Only attempt to read the first byte (the `u8`) of the storage value data + let _loaded_value: Option = ink::env::get_contract_storage(&key) + .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; + Ok(()) + } + + /// Read from the contract storage slot, consuming all the data from the buffer. + #[ink(message)] + pub fn set_and_take_storage_all_data_consumed(&self) -> Result<(), String> { + let key = 0u32; + let value = [0x42; 32]; + ink::env::set_contract_storage(&key, &value); + let loaded_value = ink::env::take_contract_storage(&key) + .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; + assert_eq!(loaded_value, Some(value)); + Ok(()) + } + + /// Read from the contract storage slot, only partially consuming data from the + /// buffer. + #[ink(message)] + pub fn set_and_take_storage_partial_data_consumed(&self) -> Result<(), String> { + let key = 0u32; + let value = [0x42; 32]; + ink::env::set_contract_storage(&key, &value); + // Only attempt to read the first byte (the `u8`) of the storage value data + let _loaded_value: Option = ink::env::take_contract_storage(&key) + .map_err(|e| format!("get_contract_storage failed: {:?}", e))?; + Ok(()) + } + } +} + +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests; diff --git a/integration-tests/contract-terminate/.gitignore b/integration-tests/contract-terminate/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/contract-terminate/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/contract-terminate/Cargo.toml b/integration-tests/contract-terminate/Cargo.toml new file mode 100644 index 00000000000..23cc5b2e65a --- /dev/null +++ b/integration-tests/contract-terminate/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "contract_terminate" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/contract-terminate/lib.rs b/integration-tests/contract-terminate/lib.rs new file mode 100644 index 00000000000..506118b1c72 --- /dev/null +++ b/integration-tests/contract-terminate/lib.rs @@ -0,0 +1,97 @@ +//! A smart contract which demonstrates behavior of the `self.env().terminate()` +//! function. It terminates itself once `terminate_me()` is called. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +pub mod just_terminates { + /// No storage is needed for this simple contract. + #[ink(storage)] + pub struct JustTerminate {} + + impl JustTerminate { + /// Creates a new instance of this contract. + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Terminates with the caller as beneficiary. + #[ink(message)] + pub fn terminate_me(&mut self) { + self.env().terminate_contract(self.env().caller()); + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn terminating_works() { + // given + let accounts = + ink::env::test::default_accounts::(); + let contract_id = ink::env::test::callee::(); + ink::env::test::set_caller::(accounts.alice); + ink::env::test::set_account_balance::( + contract_id, + 100, + ); + let mut contract = JustTerminate::new(); + + // when + let should_terminate = move || contract.terminate_me(); + + // then + ink::env::test::assert_contract_termination::( + should_terminate, + accounts.alice, + 100, + ); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_contract_terminates( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = JustTerminateRef::new(); + let contract = client + .instantiate("contract_terminate", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let terminate_me = call.terminate_me(); + let call_res = client + .call(&ink_e2e::alice(), &terminate_me) + .submit() + .await + .expect("terminate_me messages failed"); + + assert!( + call_res.return_data().is_empty(), + "Terminated contract never returns" + ); + + // then + assert!(call_res.contains_event("System", "KilledAccount")); + assert!(call_res.contains_event("Balances", "Withdraw")); + assert!(call_res.contains_event("Contracts", "Terminated")); + + Ok(()) + } + } +} diff --git a/integration-tests/contract-transfer/.gitignore b/integration-tests/contract-transfer/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/contract-transfer/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/contract-transfer/Cargo.toml b/integration-tests/contract-transfer/Cargo.toml new file mode 100644 index 00000000000..b0c9e9a94b9 --- /dev/null +++ b/integration-tests/contract-transfer/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "contract_transfer" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/contract-transfer/lib.rs b/integration-tests/contract-transfer/lib.rs new file mode 100644 index 00000000000..7fab52d44bc --- /dev/null +++ b/integration-tests/contract-transfer/lib.rs @@ -0,0 +1,264 @@ +//! A smart contract which demonstrates behavior of the `self.env().transfer()` function. +//! It transfers some of it's balance to the caller. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +pub mod give_me { + /// No storage is needed for this simple contract. + #[ink(storage)] + pub struct GiveMe {} + + impl GiveMe { + /// Creates a new instance of this contract. + #[ink(constructor, payable)] + pub fn new() -> Self { + Self {} + } + + /// Transfers `value` amount of tokens to the caller. + /// + /// # Errors + /// + /// - Panics in case the requested transfer exceeds the contract balance. + /// - Panics in case the requested transfer would have brought this contract's + /// balance below the minimum balance (i.e. the chain's existential deposit). + /// - Panics in case the transfer failed for another reason. + #[ink(message)] + pub fn give_me(&mut self, value: Balance) { + ink::env::debug_println!("requested value: {}", value); + ink::env::debug_println!("contract balance: {}", self.env().balance()); + + assert!(value <= self.env().balance(), "insufficient funds!"); + + if self.env().transfer(self.env().caller(), value).is_err() { + panic!( + "requested transfer failed. this can be the case if the contract does not\ + have sufficient free funds or if the transfer would have brought the\ + contract's balance below minimum balance." + ) + } + } + + /// Asserts that the token amount sent as payment with this call + /// is exactly `10`. This method will fail otherwise, and the + /// transaction would then be reverted. + /// + /// # Note + /// + /// The method needs to be annotated with `payable`; only then it is + /// allowed to receive value as part of the call. + #[ink(message, payable, selector = 0xCAFEBABE)] + pub fn was_it_ten(&self) { + ink::env::debug_println!( + "received payment: {}", + self.env().transferred_value() + ); + assert!(self.env().transferred_value() == 10, "payment was not ten"); + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn transfer_works() { + // given + let contract_balance = 100; + let accounts = default_accounts(); + let mut give_me = create_contract(contract_balance); + + // when + set_sender(accounts.eve); + set_balance(accounts.eve, 0); + give_me.give_me(80); + + // then + assert_eq!(get_balance(accounts.eve), 80); + } + + #[ink::test] + #[should_panic(expected = "insufficient funds!")] + fn transfer_fails_insufficient_funds() { + // given + let contract_balance = 100; + let accounts = default_accounts(); + let mut give_me = create_contract(contract_balance); + + // when + set_sender(accounts.eve); + give_me.give_me(120); + + // then + // `give_me` must already have panicked here + } + + #[ink::test] + fn test_transferred_value() { + use ink::codegen::Env; + // given + let accounts = default_accounts(); + let give_me = create_contract(100); + let contract_account = give_me.env().account_id(); + + // when + // Push the new execution context which sets initial balances and + // sets Eve as the caller + set_balance(accounts.eve, 100); + set_balance(contract_account, 0); + set_sender(accounts.eve); + + // then + // we use helper macro to emulate method invocation coming with payment, + // and there must be no panic + ink::env::pay_with_call!(give_me.was_it_ten(), 10); + + // and + // balances should be changed properly + let contract_new_balance = get_balance(contract_account); + let caller_new_balance = get_balance(accounts.eve); + + assert_eq!(caller_new_balance, 100 - 10); + assert_eq!(contract_new_balance, 10); + } + + #[ink::test] + #[should_panic(expected = "payment was not ten")] + fn test_transferred_value_must_fail() { + // given + let accounts = default_accounts(); + let give_me = create_contract(100); + + // when + // Push the new execution context which sets Eve as caller and + // the `mock_transferred_value` as the value which the contract + // will see as transferred to it. + set_sender(accounts.eve); + ink::env::test::set_value_transferred::(13); + + // then + give_me.was_it_ten(); + } + + /// Creates a new instance of `GiveMe` with `initial_balance`. + /// + /// Returns the `contract_instance`. + fn create_contract(initial_balance: Balance) -> GiveMe { + let accounts = default_accounts(); + set_sender(accounts.alice); + set_balance(contract_id(), initial_balance); + GiveMe::new() + } + + fn contract_id() -> AccountId { + ink::env::test::callee::() + } + + fn set_sender(sender: AccountId) { + ink::env::test::set_caller::(sender); + } + + fn default_accounts( + ) -> ink::env::test::DefaultAccounts { + ink::env::test::default_accounts::() + } + + fn set_balance(account_id: AccountId, balance: Balance) { + ink::env::test::set_account_balance::( + account_id, balance, + ) + } + + fn get_balance(account_id: AccountId) -> Balance { + ink::env::test::get_account_balance::( + account_id, + ) + .expect("Cannot get account balance") + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + ChainBackend, + ContractsBackend, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_sending_value_to_give_me_must_fail( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = GiveMeRef::new(); + let contract = client + .instantiate("contract_transfer", &ink_e2e::alice(), &mut constructor) + .value(1000) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let transfer = call.give_me(120); + + let call_res = client + .call(&ink_e2e::bob(), &transfer) + .value(10) + .submit() + .await; + + // then + if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_res { + assert!(dry_run.debug_message.contains("paid an unpayable message")) + } else { + panic!("Paying an unpayable message should fail") + } + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_contract_must_transfer_value_to_sender( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = GiveMeRef::new(); + let contract = client + .instantiate("contract_transfer", &ink_e2e::bob(), &mut constructor) + .value(1337) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let balance_before: Balance = client + .free_balance(contract.account_id.clone()) + .await + .expect("getting balance failed"); + + // when + let transfer = call.give_me(120); + + let call_res = client + .call(&ink_e2e::eve(), &transfer) + .submit() + .await + .expect("call failed"); + + // then + assert!(call_res.debug_message().contains("requested value: 120\n")); + + let balance_after: Balance = client + .free_balance(contract.account_id.clone()) + .await + .expect("getting balance failed"); + assert_eq!(balance_before - balance_after, 120); + + Ok(()) + } + } +} diff --git a/integration-tests/custom-allocator/.gitignore b/integration-tests/custom-allocator/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/custom-allocator/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/custom-allocator/Cargo.toml b/integration-tests/custom-allocator/Cargo.toml new file mode 100755 index 00000000000..e3315fd7ca9 --- /dev/null +++ b/integration-tests/custom-allocator/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "custom-allocator" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +# We're going to use a different allocator than the one provided by ink!. To do that we +# first need to disable the included memory allocator. +ink = { path = "../../crates/ink", default-features = false, features = ["no-allocator"] } + +# This is going to be our new global memory allocator. +dlmalloc = {version = "0.2", default-features = false, features = ["global"] } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/custom-allocator/lib.rs b/integration-tests/custom-allocator/lib.rs new file mode 100755 index 00000000000..981ab3ba4e9 --- /dev/null +++ b/integration-tests/custom-allocator/lib.rs @@ -0,0 +1,170 @@ +//! # Custom Allocator +//! +//! This example demonstrates how to opt-out of the ink! provided global memory allocator. +//! +//! We will use [`dlmalloc`](https://github.com/alexcrichton/dlmalloc-rs) instead. +//! +//! ## Warning! +//! +//! We **do not** recommend you opt-out of the provided allocator for production contract +//! deployments! +//! +//! If you don't handle allocations correctly you can introduce security vulnerabilities +//! to your contracts. +//! +//! You may also introduce performance issues. This is because the code of your allocator +//! will be included in the final contract binary, potentially increasing gas usage +//! significantly. +//! +//! ## Why Change the Allocator? +//! +//! The default memory allocator was designed to have a tiny size footprint, and made some +//! compromises to achieve that, e.g it does not free/deallocate memory. +//! +//! You may have a use case where you want to deallocate memory, or allocate it using a +//! different strategy. +//! +//! Providing your own allocator lets you choose the right tradeoffs for your use case. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +// Here we set `dlmalloc` to be the global memory allocator. +// +// The [`GlobalAlloc`](https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html) trait is +// important to understand if you're swapping our your allocator. +#[cfg(not(feature = "std"))] +#[global_allocator] +static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; + +#[ink::contract] +mod custom_allocator { + use ink::prelude::{ + vec, + vec::Vec, + }; + + #[ink(storage)] + pub struct CustomAllocator { + /// Stores a single `bool` value on the storage. + /// + /// # Note + /// + /// We're using a `Vec` here as it allocates its elements onto the heap, as + /// opposed to the stack. This allows us to demonstrate that our new + /// allocator actually works. + value: Vec, + } + + impl CustomAllocator { + /// Constructor that initializes the `bool` value to the given `init_value`. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { + value: vec![init_value], + } + } + + /// Creates a new flipper smart contract initialized to `false`. + #[ink(constructor)] + pub fn default() -> Self { + Self::new(Default::default()) + } + + /// A message that can be called on instantiated contracts. + /// This one flips the value of the stored `bool` from `true` + /// to `false` and vice versa. + #[ink(message)] + pub fn flip(&mut self) { + self.value[0] = !self.value[0]; + } + + /// Simply returns the current value of our `bool`. + #[ink(message)] + pub fn get(&self) -> bool { + self.value[0] + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + let custom_allocator = CustomAllocator::default(); + assert!(!custom_allocator.get()); + } + + #[ink::test] + fn it_works() { + let mut custom_allocator = CustomAllocator::new(false); + assert!(!custom_allocator.get()); + custom_allocator.flip(); + assert!(custom_allocator.get()); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + /// We test that we can upload and instantiate the contract using its default + /// constructor. + #[ink_e2e::test] + async fn default_works(mut client: Client) -> E2EResult<()> { + // Given + let mut constructor = CustomAllocatorRef::default(); + + // When + let contract = client + .instantiate("custom_allocator", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // Then + let get = call.get(); + let get_result = client.call(&ink_e2e::alice(), &get).dry_run().await?; + assert!(matches!(get_result.return_value(), false)); + + Ok(()) + } + + /// We test that we can read and write a value from the on-chain contract + /// contract. + #[ink_e2e::test] + async fn it_works(mut client: Client) -> E2EResult<()> { + // Given + let mut constructor = CustomAllocatorRef::new(false); + let contract = client + .instantiate("custom_allocator", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let get = call.get(); + let get_result = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_result.return_value(), false)); + + // When + let flip = call.flip(); + let _flip_result = client + .call(&ink_e2e::bob(), &flip) + .submit() + .await + .expect("flip failed"); + + // Then + let get = call.get(); + let get_result = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_result.return_value(), true)); + + Ok(()) + } + } +} diff --git a/integration-tests/custom-environment/.gitignore b/integration-tests/custom-environment/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/custom-environment/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/custom-environment/Cargo.toml b/integration-tests/custom-environment/Cargo.toml new file mode 100644 index 00000000000..d38008ea1e3 --- /dev/null +++ b/integration-tests/custom-environment/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "custom-environment" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +# Assumes that the node used in E2E testing allows for at least 6 event topics. +permissive-node = [] diff --git a/integration-tests/custom-environment/README.md b/integration-tests/custom-environment/README.md new file mode 100644 index 00000000000..8bd35b4614e --- /dev/null +++ b/integration-tests/custom-environment/README.md @@ -0,0 +1,28 @@ +# `custom-environment` example + +## What is this example about? + +It demonstrates how to use custom environment, both in the contract and in the E2E tests. + +## Chain-side configuration + +To integrate this example into Substrate you need to adjust pallet contracts configuration in your runtime: + +```rust +// In your node's runtime configuration file (runtime.rs) +parameter_types! { + pub Schedule: pallet_contracts::Schedule = pallet_contracts::Schedule:: { + limits: pallet_contracts::Limits { + event_topics: 6, + ..Default::default() + }, + ..Default::default() + }; +} + +impl pallet_contracts::Config for Runtime { + … + type Schedule = Schedule; + … +} + ``` diff --git a/integration-tests/custom-environment/lib.rs b/integration-tests/custom-environment/lib.rs new file mode 100644 index 00000000000..62b5d4e1f9a --- /dev/null +++ b/integration-tests/custom-environment/lib.rs @@ -0,0 +1,146 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::env::{DefaultEnvironment, Environment}; + +/// Our custom environment diverges from the `DefaultEnvironment` in the event topics +/// limit. +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(TypeInfo)] +pub enum EnvironmentWithManyTopics {} + +impl Environment for EnvironmentWithManyTopics { + // We allow for 5 topics in the event, therefore the contract pallet's schedule must + // allow for 6 of them (to allow the implicit topic for the event signature). + const MAX_EVENT_TOPICS: usize = + ::MAX_EVENT_TOPICS + 1; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = ::ChainExtension; +} + +#[ink::contract(env = crate::EnvironmentWithManyTopics)] +mod runtime_call { + /// Trivial contract with a single message that emits an event with many topics. + #[ink(storage)] + #[derive(Default)] + pub struct Topics; + + /// An event that would be forbidden in the default environment, but is completely + /// valid in our custom one. + #[ink(event)] + #[derive(Default)] + pub struct EventWithTopics { + #[ink(topic)] + first_topic: Balance, + #[ink(topic)] + second_topic: Balance, + #[ink(topic)] + third_topic: Balance, + #[ink(topic)] + fourth_topic: Balance, + } + + impl Topics { + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Emit an event with many topics. + #[ink(message)] + pub fn trigger(&mut self) { + self.env().emit_event(EventWithTopics::default()); + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn emits_event_with_many_topics() { + let mut contract = Topics::new(); + contract.trigger(); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 1); + + let emitted_event = ::decode( + &mut &emitted_events[0].data[..], + ); + + assert!(emitted_event.is_ok()); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = Result>; + + #[cfg(feature = "permissive-node")] + #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)] + async fn calling_custom_environment_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = TopicsRef::new(); + let contract = client + .instantiate("custom-environment", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let message = call.trigger(); + + let call_res = client + .call(&ink_e2e::alice(), &message) + .submit() + .await + .expect("call failed"); + + // then + call_res.contains_event("Contracts", "ContractEmitted"); + + Ok(()) + } + + #[cfg(not(feature = "permissive-node"))] + #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)] + async fn calling_custom_environment_fails_if_incompatible_with_node< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = TopicsRef::new(); + let contract = client + .instantiate("custom-environment", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let message = call.trigger(); + + // when + let call_res = client + .call(&ink_e2e::alice(), &message).dry_run() + .await; + + // then + assert!(call_res.is_err()); + + Ok(()) + } + } +} diff --git a/integration-tests/dns/.gitignore b/integration-tests/dns/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/dns/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/dns/Cargo.toml b/integration-tests/dns/Cargo.toml new file mode 100644 index 00000000000..007fd9203b6 --- /dev/null +++ b/integration-tests/dns/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dns" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/dns/lib.rs b/integration-tests/dns/lib.rs new file mode 100644 index 00000000000..a46cdb6e9ea --- /dev/null +++ b/integration-tests/dns/lib.rs @@ -0,0 +1,262 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod dns { + use ink::storage::Mapping; + + /// Emitted whenever a new name is being registered. + #[ink(event)] + pub struct Register { + #[ink(topic)] + name: Hash, + #[ink(topic)] + from: AccountId, + } + + /// Emitted whenever an address changes. + #[ink(event)] + pub struct SetAddress { + #[ink(topic)] + name: Hash, + from: AccountId, + #[ink(topic)] + old_address: Option, + #[ink(topic)] + new_address: AccountId, + } + + /// Emitted whenever a name is being transferred. + #[ink(event)] + pub struct Transfer { + #[ink(topic)] + name: Hash, + from: AccountId, + #[ink(topic)] + old_owner: Option, + #[ink(topic)] + new_owner: AccountId, + } + + /// Domain name service contract inspired by + /// [this blog post](https://medium.com/@chainx_org/secure-and-decentralized-polkadot-domain-name-system-e06c35c2a48d). + /// + /// # Note + /// + /// This is a port from the blog post's ink! 1.0 based version of the contract + /// to ink! 2.0. + /// + /// # Description + /// + /// The main function of this contract is domain name resolution which + /// refers to the retrieval of numeric values corresponding to readable + /// and easily memorable names such as "polka.dot" which can be used + /// to facilitate transfers, voting and DApp-related operations instead + /// of resorting to long IP addresses that are hard to remember. + #[ink(storage)] + pub struct DomainNameService { + /// A hashmap to store all name to addresses mapping. + name_to_address: Mapping, + /// A hashmap to store all name to owners mapping. + name_to_owner: Mapping, + /// The default address. + default_address: AccountId, + } + + impl Default for DomainNameService { + fn default() -> Self { + let mut name_to_address = Mapping::new(); + name_to_address.insert(Hash::default(), &zero_address()); + let mut name_to_owner = Mapping::new(); + name_to_owner.insert(Hash::default(), &zero_address()); + + Self { + name_to_address, + name_to_owner, + default_address: zero_address(), + } + } + } + + /// Errors that can occur upon calling this contract. + #[derive(Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Returned if the name already exists upon registration. + NameAlreadyExists, + /// Returned if caller is not owner while required to. + CallerIsNotOwner, + } + + /// Type alias for the contract's result type. + pub type Result = core::result::Result; + + impl DomainNameService { + /// Creates a new domain name service contract. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Register specific name with caller as owner. + #[ink(message)] + pub fn register(&mut self, name: Hash) -> Result<()> { + let caller = self.env().caller(); + if self.name_to_owner.contains(name) { + return Err(Error::NameAlreadyExists) + } + + self.name_to_owner.insert(name, &caller); + self.env().emit_event(Register { name, from: caller }); + + Ok(()) + } + + /// Set address for specific name. + #[ink(message)] + pub fn set_address(&mut self, name: Hash, new_address: AccountId) -> Result<()> { + let caller = self.env().caller(); + let owner = self.get_owner_or_default(name); + if caller != owner { + return Err(Error::CallerIsNotOwner) + } + + let old_address = self.name_to_address.get(name); + self.name_to_address.insert(name, &new_address); + + self.env().emit_event(SetAddress { + name, + from: caller, + old_address, + new_address, + }); + Ok(()) + } + + /// Transfer owner to another address. + #[ink(message)] + pub fn transfer(&mut self, name: Hash, to: AccountId) -> Result<()> { + let caller = self.env().caller(); + let owner = self.get_owner_or_default(name); + if caller != owner { + return Err(Error::CallerIsNotOwner) + } + + let old_owner = self.name_to_owner.get(name); + self.name_to_owner.insert(name, &to); + + self.env().emit_event(Transfer { + name, + from: caller, + old_owner, + new_owner: to, + }); + + Ok(()) + } + + /// Get address for specific name. + #[ink(message)] + pub fn get_address(&self, name: Hash) -> AccountId { + self.get_address_or_default(name) + } + + /// Get owner of specific name. + #[ink(message)] + pub fn get_owner(&self, name: Hash) -> AccountId { + self.get_owner_or_default(name) + } + + /// Returns the owner given the hash or the default address. + fn get_owner_or_default(&self, name: Hash) -> AccountId { + self.name_to_owner.get(name).unwrap_or(self.default_address) + } + + /// Returns the address given the hash or the default address. + fn get_address_or_default(&self, name: Hash) -> AccountId { + self.name_to_address + .get(name) + .unwrap_or(self.default_address) + } + } + + /// Helper for referencing the zero address (`0x00`). Note that in practice this + /// address should not be treated in any special way (such as a default + /// placeholder) since it has a known private key. + fn zero_address() -> AccountId { + [0u8; 32].into() + } + + #[cfg(test)] + mod tests { + use super::*; + + fn default_accounts( + ) -> ink::env::test::DefaultAccounts { + ink::env::test::default_accounts::() + } + + fn set_next_caller(caller: AccountId) { + ink::env::test::set_caller::(caller); + } + + #[ink::test] + fn register_works() { + let default_accounts = default_accounts(); + let name = Hash::from([0x99; 32]); + + set_next_caller(default_accounts.alice); + let mut contract = DomainNameService::new(); + + assert_eq!(contract.register(name), Ok(())); + assert_eq!(contract.register(name), Err(Error::NameAlreadyExists)); + } + + #[ink::test] + fn set_address_works() { + let accounts = default_accounts(); + let name = Hash::from([0x99; 32]); + + set_next_caller(accounts.alice); + + let mut contract = DomainNameService::new(); + assert_eq!(contract.register(name), Ok(())); + + // Caller is not owner, `set_address` should fail. + set_next_caller(accounts.bob); + assert_eq!( + contract.set_address(name, accounts.bob), + Err(Error::CallerIsNotOwner) + ); + + // Caller is owner, set_address will be successful + set_next_caller(accounts.alice); + assert_eq!(contract.set_address(name, accounts.bob), Ok(())); + assert_eq!(contract.get_address(name), accounts.bob); + } + + #[ink::test] + fn transfer_works() { + let accounts = default_accounts(); + let name = Hash::from([0x99; 32]); + + set_next_caller(accounts.alice); + + let mut contract = DomainNameService::new(); + assert_eq!(contract.register(name), Ok(())); + + // Test transfer of owner. + assert_eq!(contract.transfer(name, accounts.bob), Ok(())); + + // Owner is bob, alice `set_address` should fail. + assert_eq!( + contract.set_address(name, accounts.bob), + Err(Error::CallerIsNotOwner) + ); + + set_next_caller(accounts.bob); + // Now owner is bob, `set_address` should be successful. + assert_eq!(contract.set_address(name, accounts.bob), Ok(())); + assert_eq!(contract.get_address(name), accounts.bob); + } + } +} diff --git a/integration-tests/e2e-call-runtime/.gitignore b/integration-tests/e2e-call-runtime/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/e2e-call-runtime/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/e2e-call-runtime/Cargo.toml b/integration-tests/e2e-call-runtime/Cargo.toml new file mode 100644 index 00000000000..d376a169a55 --- /dev/null +++ b/integration-tests/e2e-call-runtime/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "e2e_call_runtime" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/e2e-call-runtime/lib.rs b/integration-tests/e2e-call-runtime/lib.rs new file mode 100644 index 00000000000..b3b5ffe7926 --- /dev/null +++ b/integration-tests/e2e-call-runtime/lib.rs @@ -0,0 +1,90 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod e2e_call_runtime { + #[ink(storage)] + #[derive(Default)] + pub struct Contract {} + + impl Contract { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + #[ink(message)] + pub fn get_contract_balance(&self) -> Balance { + self.env().balance() + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + subxt::dynamic::Value, + ChainBackend, + ContractsBackend, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn call_runtime_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = ContractRef::new(); + let contract = client + .instantiate("e2e_call_runtime", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + let transfer_amount = 100_000_000_000u128; + + // when + let call_data = vec![ + // A value representing a `MultiAddress`. We want the + // "Id" variant, and that will ultimately contain the + // bytes for our destination address + Value::unnamed_variant("Id", [Value::from_bytes(&contract.account_id)]), + // A value representing the amount we'd like to transfer. + Value::u128(transfer_amount), + ]; + + let get_balance = call.get_contract_balance(); + let pre_balance = client + .call(&ink_e2e::alice(), &get_balance) + .dry_run() + .await? + .return_value(); + + // Send funds from Alice to the contract using Balances::transfer + client + .runtime_call( + &ink_e2e::alice(), + "Balances", + "transfer_allow_death", + call_data, + ) + .await + .expect("runtime call failed"); + + // then + let get_balance = call.get_contract_balance(); + let get_balance_res = client + .call(&ink_e2e::alice(), &get_balance) + .dry_run() + .await?; + + assert_eq!( + get_balance_res.return_value(), + pre_balance + transfer_amount + ); + + Ok(()) + } + } +} diff --git a/integration-tests/e2e-runtime-only-backend/.gitignore b/integration-tests/e2e-runtime-only-backend/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/e2e-runtime-only-backend/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/e2e-runtime-only-backend/Cargo.toml b/integration-tests/e2e-runtime-only-backend/Cargo.toml new file mode 100644 index 00000000000..34b769e03eb --- /dev/null +++ b/integration-tests/e2e-runtime-only-backend/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "e2e-runtime-only-backend" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e", features = ["drink"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/e2e-runtime-only-backend/lib.rs b/integration-tests/e2e-runtime-only-backend/lib.rs new file mode 100644 index 00000000000..9f3a66d350e --- /dev/null +++ b/integration-tests/e2e-runtime-only-backend/lib.rs @@ -0,0 +1,159 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod flipper { + #[ink(storage)] + pub struct Flipper { + value: bool, + } + + impl Flipper { + /// Creates a new flipper smart contract initialized with the given value. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + /// Creates a new flipper smart contract initialized to `false`. + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + /// Flips the current value of the Flipper's boolean. + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + + /// Returns the current balance of the Flipper. + #[ink(message)] + pub fn get_contract_balance(&self) -> Balance { + self.env().balance() + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + subxt::dynamic::Value, + ChainBackend, + ContractsBackend, + }; + + type E2EResult = std::result::Result>; + + /// Tests standard flipper scenario: + /// - deploy the flipper contract with initial value `false` + /// - flip the flipper + /// - get the flipper's value + /// - assert that the value is `true` + #[ink_e2e::test(backend(runtime_only()))] + async fn it_works(mut client: Client) -> E2EResult<()> { + // given + const INITIAL_VALUE: bool = false; + let mut constructor = FlipperRef::new(INITIAL_VALUE); + + let contract = client + .instantiate( + "e2e-runtime-only-backend", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .expect("deploy failed"); + + // when + let mut call = contract.call::(); + let _flip_res = client.call(&ink_e2e::bob(), &call.flip()).submit().await; + + // then + let get_res = client.call(&ink_e2e::bob(), &call.get()).dry_run().await?; + assert_eq!(get_res.return_value(), !INITIAL_VALUE); + + Ok(()) + } + + /// Tests runtime call scenario: + /// - deploy the flipper contract + /// - get the contract's balance + /// - transfer some funds to the contract using runtime call + /// - get the contract's balance again + /// - assert that the contract's balance increased by the transferred amount + #[ink_e2e::test(backend(runtime_only()))] + async fn runtime_call_works() -> E2EResult<()> { + // given + let mut constructor = FlipperRef::new(false); + + let contract = client + .instantiate( + "e2e-runtime-only-backend", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .expect("deploy failed"); + let call = contract.call::(); + + let old_balance = client + .call(&ink_e2e::alice(), &call.get_contract_balance()) + .submit() + .await + .expect("get_contract_balance failed") + .return_value(); + + const ENDOWMENT: u128 = 10; + + // when + let call_data = vec![ + Value::from_bytes(&contract.account_id), + Value::u128(ENDOWMENT), + ]; + client + .runtime_call( + &ink_e2e::alice(), + "Balances", + "transfer_allow_death", + call_data, + ) + .await + .expect("runtime call failed"); + + // then + let new_balance = client + .call(&ink_e2e::alice(), &call.get_contract_balance()) + .submit() + .await + .expect("get_contract_balance failed") + .return_value(); + + assert_eq!(old_balance + ENDOWMENT, new_balance); + Ok(()) + } + + /// Just instantiate a contract using non-default runtime. + #[ink_e2e::test(backend(runtime_only(runtime = ink_e2e::MinimalRuntime)))] + async fn custom_runtime(mut client: Client) -> E2EResult<()> { + client + .instantiate( + "e2e-runtime-only-backend", + &ink_e2e::alice(), + &mut FlipperRef::new(false), + ) + .submit() + .await + .expect("instantiate failed"); + + Ok(()) + } + } +} diff --git a/integration-tests/erc1155/.gitignore b/integration-tests/erc1155/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/erc1155/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/erc1155/Cargo.toml b/integration-tests/erc1155/Cargo.toml new file mode 100644 index 00000000000..5f3e2496f90 --- /dev/null +++ b/integration-tests/erc1155/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "erc1155" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/erc1155/lib.rs b/integration-tests/erc1155/lib.rs new file mode 100644 index 00000000000..ba06c1ea052 --- /dev/null +++ b/integration-tests/erc1155/lib.rs @@ -0,0 +1,805 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::{ + prelude::vec::Vec, + primitives::AccountId, +}; + +// This is the return value that we expect if a smart contract supports receiving ERC-1155 +// tokens. +// +// It is calculated with +// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`, and +// corresponds to 0xf23a6e61. +#[cfg_attr(test, allow(dead_code))] +const ON_ERC_1155_RECEIVED_SELECTOR: [u8; 4] = [0xF2, 0x3A, 0x6E, 0x61]; + +// This is the return value that we expect if a smart contract supports batch receiving +// ERC-1155 tokens. +// +// It is calculated with +// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)" +// ))`, and corresponds to 0xbc197c81. +const _ON_ERC_1155_BATCH_RECEIVED_SELECTOR: [u8; 4] = [0xBC, 0x19, 0x7C, 0x81]; + +/// A type representing the unique IDs of tokens managed by this contract. +pub type TokenId = u128; + +type Balance = ::Balance; + +// The ERC-1155 error types. +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum Error { + /// This token ID has not yet been created by the contract. + UnexistentToken, + /// The caller tried to sending tokens to the zero-address (`0x00`). + ZeroAddressTransfer, + /// The caller is not approved to transfer tokens on behalf of the account. + NotApproved, + /// The account does not have enough funds to complete the transfer. + InsufficientBalance, + /// An account does not need to approve themselves to transfer tokens. + SelfApproval, + /// The number of tokens being transferred does not match the specified number of + /// transfers. + BatchTransferMismatch, +} + +// The ERC-1155 result types. +pub type Result = core::result::Result; + +/// Evaluate `$x:expr` and if not true return `Err($y:expr)`. +/// +/// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`. +macro_rules! ensure { + ( $condition:expr, $error:expr $(,)? ) => {{ + if !$condition { + return ::core::result::Result::Err(::core::convert::Into::into($error)) + } + }}; +} + +/// The interface for an ERC-1155 compliant contract. +/// +/// The interface is defined here: . +/// +/// The goal of ERC-1155 is to allow a single contract to manage a variety of assets. +/// These assets can be fungible, non-fungible, or a combination. +/// +/// By tracking multiple assets the ERC-1155 standard is able to support batch transfers, +/// which make it easy to transfer a mix of multiple tokens at once. +#[ink::trait_definition] +pub trait Erc1155 { + /// Transfer a `value` amount of `token_id` tokens to the `to` account from the `from` + /// account. + /// + /// Note that the call does not have to originate from the `from` account, and may + /// originate from any account which is approved to transfer `from`'s tokens. + #[ink(message)] + fn safe_transfer_from( + &mut self, + from: AccountId, + to: AccountId, + token_id: TokenId, + value: Balance, + data: Vec, + ) -> Result<()>; + + /// Perform a batch transfer of `token_ids` to the `to` account from the `from` + /// account. + /// + /// The number of `values` specified to be transferred must match the number of + /// `token_ids`, otherwise this call will revert. + /// + /// Note that the call does not have to originate from the `from` account, and may + /// originate from any account which is approved to transfer `from`'s tokens. + #[ink(message)] + fn safe_batch_transfer_from( + &mut self, + from: AccountId, + to: AccountId, + token_ids: Vec, + values: Vec, + data: Vec, + ) -> Result<()>; + + /// Query the balance of a specific token for the provided account. + #[ink(message)] + fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance; + + /// Query the balances for a set of tokens for a set of accounts. + /// + /// E.g use this call if you want to query what Alice and Bob's balances are for + /// Tokens ID 1 and ID 2. + /// + /// This will return all the balances for a given owner before moving on to the next + /// owner. In the example above this means that the return value should look like: + /// + /// [Alice Balance of Token ID 1, Alice Balance of Token ID 2, Bob Balance of Token ID + /// 1, Bob Balance of Token ID 2] + #[ink(message)] + fn balance_of_batch( + &self, + owners: Vec, + token_ids: Vec, + ) -> Vec; + + /// Enable or disable a third party, known as an `operator`, to control all tokens on + /// behalf of the caller. + #[ink(message)] + fn set_approval_for_all(&mut self, operator: AccountId, approved: bool) + -> Result<()>; + + /// Query if the given `operator` is allowed to control all of `owner`'s tokens. + #[ink(message)] + fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool; +} + +/// The interface for an ERC-1155 Token Receiver contract. +/// +/// The interface is defined here: . +/// +/// Smart contracts which want to accept token transfers must implement this interface. By +/// default if a contract does not support this interface any transactions originating +/// from an ERC-1155 compliant contract which attempt to transfer tokens directly to the +/// contract's address must be reverted. +#[ink::trait_definition] +pub trait Erc1155TokenReceiver { + /// Handle the receipt of a single ERC-1155 token. + /// + /// This should be called by a compliant ERC-1155 contract if the intended recipient + /// is a smart contract. + /// + /// If the smart contract implementing this interface accepts token transfers then it + /// must return `ON_ERC_1155_RECEIVED_SELECTOR` from this function. To reject a + /// transfer it must revert. + /// + /// Any callers must revert if they receive anything other than + /// `ON_ERC_1155_RECEIVED_SELECTOR` as a return value. + #[ink(message, selector = 0xF23A6E61)] + fn on_received( + &mut self, + operator: AccountId, + from: AccountId, + token_id: TokenId, + value: Balance, + data: Vec, + ) -> Vec; + + /// Handle the receipt of multiple ERC-1155 tokens. + /// + /// This should be called by a compliant ERC-1155 contract if the intended recipient + /// is a smart contract. + /// + /// If the smart contract implementing this interface accepts token transfers then it + /// must return `BATCH_ON_ERC_1155_RECEIVED_SELECTOR` from this function. To + /// reject a transfer it must revert. + /// + /// Any callers must revert if they receive anything other than + /// `BATCH_ON_ERC_1155_RECEIVED_SELECTOR` as a return value. + #[ink(message, selector = 0xBC197C81)] + fn on_batch_received( + &mut self, + operator: AccountId, + from: AccountId, + token_ids: Vec, + values: Vec, + data: Vec, + ) -> Vec; +} + +#[ink::contract] +mod erc1155 { + use super::*; + + use ink::storage::Mapping; + + type Owner = AccountId; + type Operator = AccountId; + + /// Indicate that a token transfer has occured. + /// + /// This must be emitted even if a zero value transfer occurs. + #[ink(event)] + pub struct TransferSingle { + #[ink(topic)] + operator: Option, + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + token_id: TokenId, + value: Balance, + } + + /// Indicate that an approval event has happened. + #[ink(event)] + pub struct ApprovalForAll { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + operator: AccountId, + approved: bool, + } + + /// Indicate that a token's URI has been updated. + #[ink(event)] + pub struct Uri { + value: ink::prelude::string::String, + #[ink(topic)] + token_id: TokenId, + } + + /// An ERC-1155 contract. + #[ink(storage)] + #[derive(Default)] + pub struct Contract { + /// Tracks the balances of accounts across the different tokens that they might + /// be holding. + balances: Mapping<(AccountId, TokenId), Balance>, + /// Which accounts (called operators) have been approved to spend funds on behalf + /// of an owner. + approvals: Mapping<(Owner, Operator), ()>, + /// A unique identifier for the tokens which have been minted (and are therefore + /// supported) by this contract. + token_id_nonce: TokenId, + } + + impl Contract { + /// Initialize a default instance of this ERC-1155 implementation. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Create the initial supply for a token. + /// + /// The initial supply will be provided to the caller (a.k.a the minter), and the + /// `token_id` will be assigned by the smart contract. + /// + /// Note that as implemented anyone can create tokens. If you were to instantiate + /// this contract in a production environment you'd probably want to lock down + /// the addresses that are allowed to create tokens. + #[ink(message)] + pub fn create(&mut self, value: Balance) -> TokenId { + let caller = self.env().caller(); + + // Given that TokenId is a `u128` the likelihood of this overflowing is pretty + // slim. + #[allow(clippy::arithmetic_side_effects)] + { + self.token_id_nonce += 1; + } + self.balances.insert((caller, self.token_id_nonce), &value); + + // Emit transfer event but with mint semantics + self.env().emit_event(TransferSingle { + operator: Some(caller), + from: None, + to: if value == 0 { None } else { Some(caller) }, + token_id: self.token_id_nonce, + value, + }); + + self.token_id_nonce + } + + /// Mint a `value` amount of `token_id` tokens. + /// + /// It is assumed that the token has already been `create`-ed. The newly minted + /// supply will be assigned to the caller (a.k.a the minter). + /// + /// Note that as implemented anyone can mint tokens. If you were to instantiate + /// this contract in a production environment you'd probably want to lock down + /// the addresses that are allowed to mint tokens. + #[ink(message)] + pub fn mint(&mut self, token_id: TokenId, value: Balance) -> Result<()> { + ensure!(token_id <= self.token_id_nonce, Error::UnexistentToken); + + let caller = self.env().caller(); + self.balances.insert((caller, token_id), &value); + + // Emit transfer event but with mint semantics + self.env().emit_event(TransferSingle { + operator: Some(caller), + from: None, + to: Some(caller), + token_id, + value, + }); + + Ok(()) + } + + // Helper function for performing single token transfers. + // + // Should not be used directly since it's missing certain checks which are + // important to the ERC-1155 standard (it is expected that the caller has + // already performed these). + // + // # Panics + // + // If `from` does not hold any `token_id` tokens. + fn perform_transfer( + &mut self, + from: AccountId, + to: AccountId, + token_id: TokenId, + value: Balance, + ) { + let mut sender_balance = self + .balances + .get((from, token_id)) + .expect("Caller should have ensured that `from` holds `token_id`."); + // checks that sender_balance >= value were performed by caller + #[allow(clippy::arithmetic_side_effects)] + { + sender_balance -= value; + } + self.balances.insert((from, token_id), &sender_balance); + + let mut recipient_balance = self.balances.get((to, token_id)).unwrap_or(0); + recipient_balance = recipient_balance.checked_add(value).unwrap(); + self.balances.insert((to, token_id), &recipient_balance); + + let caller = self.env().caller(); + self.env().emit_event(TransferSingle { + operator: Some(caller), + from: Some(from), + to: Some(to), + token_id, + value, + }); + } + + // Check if the address at `to` is a smart contract which accepts ERC-1155 token + // transfers. + // + // If they're a smart contract which **doesn't** accept tokens transfers this call + // will revert. Otherwise we risk locking user funds at in that contract + // with no chance of recovery. + #[cfg_attr(test, allow(unused_variables))] + fn transfer_acceptance_check( + &mut self, + caller: AccountId, + from: AccountId, + to: AccountId, + token_id: TokenId, + value: Balance, + data: Vec, + ) { + // This is disabled during tests due to the use of `invoke_contract()` not + // being supported (tests end up panicking). + #[cfg(not(test))] + { + use ink::env::call::{ + build_call, + ExecutionInput, + Selector, + }; + + // If our recipient is a smart contract we need to see if they accept or + // reject this transfer. If they reject it we need to revert the call. + let result = build_call::() + .call(to) + .gas_limit(5000) + .exec_input( + ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR)) + .push_arg(caller) + .push_arg(from) + .push_arg(token_id) + .push_arg(value) + .push_arg(data), + ) + .returns::>() + .params() + .try_invoke(); + + match result { + Ok(v) => { + ink::env::debug_println!( + "Received return value \"{:?}\" from contract {:?}", + v.clone().expect( + "Call should be valid, don't expect a `LangError`." + ), + from + ); + assert_eq!( + v.clone().expect("Call should be valid, don't expect a `LangError`."), + &ON_ERC_1155_RECEIVED_SELECTOR[..], + "The recipient contract at {to:?} does not accept token transfers.\n + Expected: {ON_ERC_1155_RECEIVED_SELECTOR:?}, Got {v:?}" + ) + } + Err(e) => { + use ink::env::ReturnErrorCode; + + match e { + ink::env::Error::ReturnError( + ReturnErrorCode::CodeNotFound + | ReturnErrorCode::NotCallable, + ) => { + // Our recipient wasn't a smart contract, so there's + // nothing more for + // us to do + ink::env::debug_println!("Recipient at {:?} from is not a smart contract ({:?})", from, e); + } + _ => { + // We got some sort of error from the call to our + // recipient smart + // contract, and as such we must revert this call + panic!( + "Got error \"{e:?}\" while trying to call {from:?}" + ) + } + } + } + } + } + } + } + + impl super::Erc1155 for Contract { + #[ink(message)] + fn safe_transfer_from( + &mut self, + from: AccountId, + to: AccountId, + token_id: TokenId, + value: Balance, + data: Vec, + ) -> Result<()> { + let caller = self.env().caller(); + if caller != from { + ensure!(self.is_approved_for_all(from, caller), Error::NotApproved); + } + + ensure!(to != zero_address(), Error::ZeroAddressTransfer); + + let balance = self.balance_of(from, token_id); + ensure!(balance >= value, Error::InsufficientBalance); + + self.perform_transfer(from, to, token_id, value); + self.transfer_acceptance_check(caller, from, to, token_id, value, data); + + Ok(()) + } + + #[ink(message)] + fn safe_batch_transfer_from( + &mut self, + from: AccountId, + to: AccountId, + token_ids: Vec, + values: Vec, + data: Vec, + ) -> Result<()> { + let caller = self.env().caller(); + if caller != from { + ensure!(self.is_approved_for_all(from, caller), Error::NotApproved); + } + + ensure!(to != zero_address(), Error::ZeroAddressTransfer); + ensure!(!token_ids.is_empty(), Error::BatchTransferMismatch); + ensure!( + token_ids.len() == values.len(), + Error::BatchTransferMismatch, + ); + + let transfers = token_ids.iter().zip(values.iter()); + for (&id, &v) in transfers.clone() { + let balance = self.balance_of(from, id); + ensure!(balance >= v, Error::InsufficientBalance); + } + + for (&id, &v) in transfers { + self.perform_transfer(from, to, id, v); + } + + // Can use the any token ID/value here, we really just care about knowing if + // `to` is a smart contract which accepts transfers + self.transfer_acceptance_check( + caller, + from, + to, + token_ids[0], + values[0], + data, + ); + + Ok(()) + } + + #[ink(message)] + fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance { + self.balances.get((owner, token_id)).unwrap_or(0) + } + + #[ink(message)] + fn balance_of_batch( + &self, + owners: Vec, + token_ids: Vec, + ) -> Vec { + let mut output = Vec::new(); + for o in &owners { + for t in &token_ids { + let amount = self.balance_of(*o, *t); + output.push(amount); + } + } + output + } + + #[ink(message)] + fn set_approval_for_all( + &mut self, + operator: AccountId, + approved: bool, + ) -> Result<()> { + let caller = self.env().caller(); + ensure!(operator != caller, Error::SelfApproval); + + if approved { + self.approvals.insert((&caller, &operator), &()); + } else { + self.approvals.remove((&caller, &operator)); + } + + self.env().emit_event(ApprovalForAll { + owner: caller, + operator, + approved, + }); + + Ok(()) + } + + #[ink(message)] + fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { + self.approvals.contains((&owner, &operator)) + } + } + + impl super::Erc1155TokenReceiver for Contract { + #[ink(message, selector = 0xF23A6E61)] + fn on_received( + &mut self, + _operator: AccountId, + _from: AccountId, + _token_id: TokenId, + _value: Balance, + _data: Vec, + ) -> Vec { + // The ERC-1155 standard dictates that if a contract does not accept token + // transfers directly to the contract, then the contract must + // revert. + // + // This prevents a user from unintentionally transferring tokens to a smart + // contract and getting their funds stuck without any sort of + // recovery mechanism. + // + // Note that the choice of whether or not to accept tokens is implementation + // specific, and we've decided to not accept them in this + // implementation. + unimplemented!("This smart contract does not accept token transfer.") + } + + #[ink(message, selector = 0xBC197C81)] + fn on_batch_received( + &mut self, + _operator: AccountId, + _from: AccountId, + _token_ids: Vec, + _values: Vec, + _data: Vec, + ) -> Vec { + // The ERC-1155 standard dictates that if a contract does not accept token + // transfers directly to the contract, then the contract must + // revert. + // + // This prevents a user from unintentionally transferring tokens to a smart + // contract and getting their funds stuck without any sort of + // recovery mechanism. + // + // Note that the choice of whether or not to accept tokens is implementation + // specific, and we've decided to not accept them in this + // implementation. + unimplemented!("This smart contract does not accept batch token transfers.") + } + } + + /// Helper for referencing the zero address (`0x00`). Note that in practice this + /// address should not be treated in any special way (such as a default + /// placeholder) since it has a known private key. + fn zero_address() -> AccountId { + [0u8; 32].into() + } + + #[cfg(test)] + mod tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + use crate::Erc1155; + + fn set_sender(sender: AccountId) { + ink::env::test::set_caller::(sender); + } + + fn default_accounts() -> ink::env::test::DefaultAccounts { + ink::env::test::default_accounts::() + } + + fn alice() -> AccountId { + default_accounts().alice + } + + fn bob() -> AccountId { + default_accounts().bob + } + + fn charlie() -> AccountId { + default_accounts().charlie + } + + fn init_contract() -> Contract { + let mut erc = Contract::new(); + erc.balances.insert((alice(), 1), &10); + erc.balances.insert((alice(), 2), &20); + erc.balances.insert((bob(), 1), &10); + + erc + } + + #[ink::test] + fn can_get_correct_balance_of() { + let erc = init_contract(); + + assert_eq!(erc.balance_of(alice(), 1), 10); + assert_eq!(erc.balance_of(alice(), 2), 20); + assert_eq!(erc.balance_of(alice(), 3), 0); + assert_eq!(erc.balance_of(bob(), 2), 0); + } + + #[ink::test] + fn can_get_correct_batch_balance_of() { + let erc = init_contract(); + + assert_eq!( + erc.balance_of_batch(vec![alice()], vec![1, 2, 3]), + vec![10, 20, 0] + ); + assert_eq!( + erc.balance_of_batch(vec![alice(), bob()], vec![1]), + vec![10, 10] + ); + + assert_eq!( + erc.balance_of_batch(vec![alice(), bob(), charlie()], vec![1, 2]), + vec![10, 20, 10, 0, 0, 0] + ); + } + + #[ink::test] + fn can_send_tokens_between_accounts() { + let mut erc = init_contract(); + + assert!(erc.safe_transfer_from(alice(), bob(), 1, 5, vec![]).is_ok()); + assert_eq!(erc.balance_of(alice(), 1), 5); + assert_eq!(erc.balance_of(bob(), 1), 15); + + assert!(erc.safe_transfer_from(alice(), bob(), 2, 5, vec![]).is_ok()); + assert_eq!(erc.balance_of(alice(), 2), 15); + assert_eq!(erc.balance_of(bob(), 2), 5); + } + + #[ink::test] + fn sending_too_many_tokens_fails() { + let mut erc = init_contract(); + let res = erc.safe_transfer_from(alice(), bob(), 1, 99, vec![]); + assert_eq!(res.unwrap_err(), Error::InsufficientBalance); + } + + #[ink::test] + fn sending_tokens_to_zero_address_fails() { + let burn: AccountId = [0; 32].into(); + + let mut erc = init_contract(); + let res = erc.safe_transfer_from(alice(), burn, 1, 10, vec![]); + assert_eq!(res.unwrap_err(), Error::ZeroAddressTransfer); + } + + #[ink::test] + fn can_send_batch_tokens() { + let mut erc = init_contract(); + assert!(erc + .safe_batch_transfer_from(alice(), bob(), vec![1, 2], vec![5, 10], vec![]) + .is_ok()); + + let balances = erc.balance_of_batch(vec![alice(), bob()], vec![1, 2]); + assert_eq!(balances, vec![5, 10, 15, 10]) + } + + #[ink::test] + fn rejects_batch_if_lengths_dont_match() { + let mut erc = init_contract(); + let res = erc.safe_batch_transfer_from( + alice(), + bob(), + vec![1, 2, 3], + vec![5], + vec![], + ); + assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch); + } + + #[ink::test] + fn batch_transfers_fail_if_len_is_zero() { + let mut erc = init_contract(); + let res = + erc.safe_batch_transfer_from(alice(), bob(), vec![], vec![], vec![]); + assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch); + } + + #[ink::test] + fn operator_can_send_tokens() { + let mut erc = init_contract(); + + let owner = alice(); + let operator = bob(); + + set_sender(owner); + assert!(erc.set_approval_for_all(operator, true).is_ok()); + + set_sender(operator); + assert!(erc + .safe_transfer_from(owner, charlie(), 1, 5, vec![]) + .is_ok()); + assert_eq!(erc.balance_of(alice(), 1), 5); + assert_eq!(erc.balance_of(charlie(), 1), 5); + } + + #[ink::test] + fn approvals_work() { + let mut erc = init_contract(); + let owner = alice(); + let operator = bob(); + let another_operator = charlie(); + + // Note: All of these tests are from the context of the owner who is either + // allowing or disallowing an operator to control their funds. + set_sender(owner); + assert!(!erc.is_approved_for_all(owner, operator)); + + assert!(erc.set_approval_for_all(operator, true).is_ok()); + assert!(erc.is_approved_for_all(owner, operator)); + + assert!(erc.set_approval_for_all(another_operator, true).is_ok()); + assert!(erc.is_approved_for_all(owner, another_operator)); + + assert!(erc.set_approval_for_all(operator, false).is_ok()); + assert!(!erc.is_approved_for_all(owner, operator)); + } + + #[ink::test] + fn minting_tokens_works() { + let mut erc = Contract::new(); + + set_sender(alice()); + assert_eq!(erc.create(0), 1); + assert_eq!(erc.balance_of(alice(), 1), 0); + + assert!(erc.mint(1, 123).is_ok()); + assert_eq!(erc.balance_of(alice(), 1), 123); + } + + #[ink::test] + fn minting_not_allowed_for_nonexistent_tokens() { + let mut erc = Contract::new(); + + let res = erc.mint(1, 123); + assert_eq!(res.unwrap_err(), Error::UnexistentToken); + } + } +} diff --git a/integration-tests/erc20/.gitignore b/integration-tests/erc20/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/erc20/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/erc20/Cargo.toml b/integration-tests/erc20/Cargo.toml new file mode 100644 index 00000000000..0991702fd83 --- /dev/null +++ b/integration-tests/erc20/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "erc20" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/erc20/lib.rs b/integration-tests/erc20/lib.rs new file mode 100644 index 00000000000..d0b0cdb7fda --- /dev/null +++ b/integration-tests/erc20/lib.rs @@ -0,0 +1,643 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod erc20 { + use ink::storage::Mapping; + + /// A simple ERC-20 contract. + #[ink(storage)] + #[derive(Default)] + pub struct Erc20 { + /// Total token supply. + total_supply: Balance, + /// Mapping from owner to number of owned token. + balances: Mapping, + /// Mapping of the token amount which an account is allowed to withdraw + /// from another account. + allowances: Mapping<(AccountId, AccountId), Balance>, + } + + /// Event emitted when a token transfer occurs. + #[ink(event)] + pub struct Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + value: Balance, + } + + /// Event emitted when an approval occurs that `spender` is allowed to withdraw + /// up to the amount of `value` tokens from `owner`. + #[ink(event)] + pub struct Approval { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + spender: AccountId, + value: Balance, + } + + /// The ERC-20 error types. + #[derive(Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Returned if not enough balance to fulfill a request is available. + InsufficientBalance, + /// Returned if not enough allowance to fulfill a request is available. + InsufficientAllowance, + } + + /// The ERC-20 result type. + pub type Result = core::result::Result; + + impl Erc20 { + /// Creates a new ERC-20 contract with the specified initial supply. + #[ink(constructor)] + pub fn new(total_supply: Balance) -> Self { + let mut balances = Mapping::default(); + let caller = Self::env().caller(); + balances.insert(caller, &total_supply); + Self::env().emit_event(Transfer { + from: None, + to: Some(caller), + value: total_supply, + }); + Self { + total_supply, + balances, + allowances: Default::default(), + } + } + + /// Returns the total token supply. + #[ink(message)] + pub fn total_supply(&self) -> Balance { + self.total_supply + } + + /// Returns the account balance for the specified `owner`. + /// + /// Returns `0` if the account is non-existent. + #[ink(message)] + pub fn balance_of(&self, owner: AccountId) -> Balance { + self.balance_of_impl(&owner) + } + + /// Returns the account balance for the specified `owner`. + /// + /// Returns `0` if the account is non-existent. + /// + /// # Note + /// + /// Prefer to call this method over `balance_of` since this + /// works using references which are more efficient in Wasm. + #[inline] + fn balance_of_impl(&self, owner: &AccountId) -> Balance { + self.balances.get(owner).unwrap_or_default() + } + + /// Returns the amount which `spender` is still allowed to withdraw from `owner`. + /// + /// Returns `0` if no allowance has been set. + #[ink(message)] + pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { + self.allowance_impl(&owner, &spender) + } + + /// Returns the amount which `spender` is still allowed to withdraw from `owner`. + /// + /// Returns `0` if no allowance has been set. + /// + /// # Note + /// + /// Prefer to call this method over `allowance` since this + /// works using references which are more efficient in Wasm. + #[inline] + fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance { + self.allowances.get((owner, spender)).unwrap_or_default() + } + + /// Transfers `value` amount of tokens from the caller's account to account `to`. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `InsufficientBalance` error if there are not enough tokens on + /// the caller's account balance. + #[ink(message)] + pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> { + let from = self.env().caller(); + self.transfer_from_to(&from, &to, value) + } + + /// Allows `spender` to withdraw from the caller's account multiple times, up to + /// the `value` amount. + /// + /// If this function is called again it overwrites the current allowance with + /// `value`. + /// + /// An `Approval` event is emitted. + #[ink(message)] + pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { + let owner = self.env().caller(); + self.allowances.insert((&owner, &spender), &value); + self.env().emit_event(Approval { + owner, + spender, + value, + }); + Ok(()) + } + + /// Transfers `value` tokens on the behalf of `from` to the account `to`. + /// + /// This can be used to allow a contract to transfer tokens on ones behalf and/or + /// to charge fees in sub-currencies, for example. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `InsufficientAllowance` error if there are not enough tokens allowed + /// for the caller to withdraw from `from`. + /// + /// Returns `InsufficientBalance` error if there are not enough tokens on + /// the account balance of `from`. + #[ink(message)] + pub fn transfer_from( + &mut self, + from: AccountId, + to: AccountId, + value: Balance, + ) -> Result<()> { + let caller = self.env().caller(); + let allowance = self.allowance_impl(&from, &caller); + if allowance < value { + return Err(Error::InsufficientAllowance) + } + self.transfer_from_to(&from, &to, value)?; + // We checked that allowance >= value + #[allow(clippy::arithmetic_side_effects)] + self.allowances + .insert((&from, &caller), &(allowance - value)); + Ok(()) + } + + /// Transfers `value` amount of tokens from the caller's account to account `to`. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `InsufficientBalance` error if there are not enough tokens on + /// the caller's account balance. + fn transfer_from_to( + &mut self, + from: &AccountId, + to: &AccountId, + value: Balance, + ) -> Result<()> { + let from_balance = self.balance_of_impl(from); + if from_balance < value { + return Err(Error::InsufficientBalance) + } + // We checked that from_balance >= value + #[allow(clippy::arithmetic_side_effects)] + self.balances.insert(from, &(from_balance - value)); + let to_balance = self.balance_of_impl(to); + self.balances + .insert(to, &(to_balance.checked_add(value).unwrap())); + self.env().emit_event(Transfer { + from: Some(*from), + to: Some(*to), + value, + }); + Ok(()) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + use ink::primitives::{ + Clear, + Hash, + }; + + fn assert_transfer_event( + event: &ink::env::test::EmittedEvent, + expected_from: Option, + expected_to: Option, + expected_value: Balance, + ) { + let decoded_event = + ::decode(&mut &event.data[..]) + .expect("encountered invalid contract event data buffer"); + let Transfer { from, to, value } = decoded_event; + assert_eq!(from, expected_from, "encountered invalid Transfer.from"); + assert_eq!(to, expected_to, "encountered invalid Transfer.to"); + assert_eq!(value, expected_value, "encountered invalid Trasfer.value"); + + let mut expected_topics = Vec::new(); + expected_topics.push( + ink::blake2x256!("Transfer(Option,Option,Balance)") + .into(), + ); + if let Some(from) = expected_from { + expected_topics.push(encoded_into_hash(from)); + } else { + expected_topics.push(Hash::CLEAR_HASH); + } + if let Some(to) = expected_to { + expected_topics.push(encoded_into_hash(to)); + } else { + expected_topics.push(Hash::CLEAR_HASH); + } + expected_topics.push(encoded_into_hash(value)); + + let topics = event.topics.clone(); + for (n, (actual_topic, expected_topic)) in + topics.iter().zip(expected_topics).enumerate() + { + let mut topic_hash = Hash::CLEAR_HASH; + let len = actual_topic.len(); + topic_hash.as_mut()[0..len].copy_from_slice(&actual_topic[0..len]); + + assert_eq!( + topic_hash, expected_topic, + "encountered invalid topic at {n}" + ); + } + } + + /// The default constructor does its job. + #[ink::test] + fn new_works() { + // Constructor works. + let _erc20 = Erc20::new(100); + + // Transfer event triggered during initial construction. + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + } + + /// The total supply was applied. + #[ink::test] + fn total_supply_works() { + // Constructor works. + let erc20 = Erc20::new(100); + // Transfer event triggered during initial construction. + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + // Get the token total supply. + assert_eq!(erc20.total_supply(), 100); + } + + /// Get the actual balance of an account. + #[ink::test] + fn balance_of_works() { + // Constructor works + let erc20 = Erc20::new(100); + // Transfer event triggered during initial construction + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + let accounts = + ink::env::test::default_accounts::(); + // Alice owns all the tokens on contract instantiation + assert_eq!(erc20.balance_of(accounts.alice), 100); + // Bob does not owns tokens + assert_eq!(erc20.balance_of(accounts.bob), 0); + } + + #[ink::test] + fn transfer_works() { + // Constructor works. + let mut erc20 = Erc20::new(100); + // Transfer event triggered during initial construction. + let accounts = + ink::env::test::default_accounts::(); + + assert_eq!(erc20.balance_of(accounts.bob), 0); + // Alice transfers 10 tokens to Bob. + assert_eq!(erc20.transfer(accounts.bob, 10), Ok(())); + // Bob owns 10 tokens. + assert_eq!(erc20.balance_of(accounts.bob), 10); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + // Check first transfer event related to ERC-20 instantiation. + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + // Check the second transfer event relating to the actual trasfer. + assert_transfer_event( + &emitted_events[1], + Some(AccountId::from([0x01; 32])), + Some(AccountId::from([0x02; 32])), + 10, + ); + } + + #[ink::test] + fn invalid_transfer_should_fail() { + // Constructor works. + let mut erc20 = Erc20::new(100); + let accounts = + ink::env::test::default_accounts::(); + + assert_eq!(erc20.balance_of(accounts.bob), 0); + + // Set the contract as callee and Bob as caller. + let contract = ink::env::account_id::(); + ink::env::test::set_callee::(contract); + ink::env::test::set_caller::(accounts.bob); + + // Bob fails to transfers 10 tokens to Eve. + assert_eq!( + erc20.transfer(accounts.eve, 10), + Err(Error::InsufficientBalance) + ); + // Alice owns all the tokens. + assert_eq!(erc20.balance_of(accounts.alice), 100); + assert_eq!(erc20.balance_of(accounts.bob), 0); + assert_eq!(erc20.balance_of(accounts.eve), 0); + + // Transfer event triggered during initial construction. + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 1); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + } + + #[ink::test] + fn transfer_from_works() { + // Constructor works. + let mut erc20 = Erc20::new(100); + // Transfer event triggered during initial construction. + let accounts = + ink::env::test::default_accounts::(); + + // Bob fails to transfer tokens owned by Alice. + assert_eq!( + erc20.transfer_from(accounts.alice, accounts.eve, 10), + Err(Error::InsufficientAllowance) + ); + // Alice approves Bob for token transfers on her behalf. + assert_eq!(erc20.approve(accounts.bob, 10), Ok(())); + + // The approve event takes place. + assert_eq!(ink::env::test::recorded_events().count(), 2); + + // Set the contract as callee and Bob as caller. + let contract = ink::env::account_id::(); + ink::env::test::set_callee::(contract); + ink::env::test::set_caller::(accounts.bob); + + // Bob transfers tokens from Alice to Eve. + assert_eq!( + erc20.transfer_from(accounts.alice, accounts.eve, 10), + Ok(()) + ); + // Eve owns tokens. + assert_eq!(erc20.balance_of(accounts.eve), 10); + + // Check all transfer events that happened during the previous calls: + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 3); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + // The second event `emitted_events[1]` is an Approve event that we skip + // checking. + assert_transfer_event( + &emitted_events[2], + Some(AccountId::from([0x01; 32])), + Some(AccountId::from([0x05; 32])), + 10, + ); + } + + #[ink::test] + fn allowance_must_not_change_on_failed_transfer() { + let mut erc20 = Erc20::new(100); + let accounts = + ink::env::test::default_accounts::(); + + // Alice approves Bob for token transfers on her behalf. + let alice_balance = erc20.balance_of(accounts.alice); + let initial_allowance = alice_balance + 2; + assert_eq!(erc20.approve(accounts.bob, initial_allowance), Ok(())); + + // Get contract address. + let callee = ink::env::account_id::(); + ink::env::test::set_callee::(callee); + ink::env::test::set_caller::(accounts.bob); + + // Bob tries to transfer tokens from Alice to Eve. + let emitted_events_before = ink::env::test::recorded_events().count(); + assert_eq!( + erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1), + Err(Error::InsufficientBalance) + ); + // Allowance must have stayed the same + assert_eq!( + erc20.allowance(accounts.alice, accounts.bob), + initial_allowance + ); + // No more events must have been emitted + assert_eq!( + emitted_events_before, + ink::env::test::recorded_events().count() + ) + } + + fn encoded_into_hash(entity: T) -> Hash + where + T: ink::scale::Encode, + { + use ink::{ + env::hash::{ + Blake2x256, + CryptoHash, + HashOutput, + }, + primitives::Clear, + }; + + let mut result = Hash::CLEAR_HASH; + let len_result = result.as_ref().len(); + let encoded = entity.encode(); + let len_encoded = encoded.len(); + if len_encoded <= len_result { + result.as_mut()[..len_encoded].copy_from_slice(&encoded); + return result + } + let mut hash_output = + <::Type as Default>::default(); + ::hash(&encoded, &mut hash_output); + let copy_len = core::cmp::min(hash_output.len(), len_result); + result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]); + result + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_transfer(mut client: Client) -> E2EResult<()> { + // given + let total_supply = 1_000_000_000; + let mut constructor = Erc20Ref::new(total_supply); + let erc20 = client + .instantiate("erc20", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = erc20.call::(); + + // when + let total_supply_msg = call.total_supply(); + let total_supply_res = client + .call(&ink_e2e::bob(), &total_supply_msg) + .dry_run() + .await?; + + let bob_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Bob); + let transfer_to_bob = 500_000_000u128; + let transfer = call.transfer(bob_account, transfer_to_bob); + let _transfer_res = client + .call(&ink_e2e::alice(), &transfer) + .submit() + .await + .expect("transfer failed"); + + let balance_of = call.balance_of(bob_account); + let balance_of_res = client + .call(&ink_e2e::alice(), &balance_of) + .dry_run() + .await?; + + // then + assert_eq!( + total_supply, + total_supply_res.return_value(), + "total_supply" + ); + assert_eq!(transfer_to_bob, balance_of_res.return_value(), "balance_of"); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_allowances(mut client: Client) -> E2EResult<()> { + // given + let total_supply = 1_000_000_000; + let mut constructor = Erc20Ref::new(total_supply); + let erc20 = client + .instantiate("erc20", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = erc20.call::(); + + // when + + let bob_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Bob); + let charlie_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Charlie); + + let amount = 500_000_000u128; + // tx + let transfer_from = call.transfer_from(bob_account, charlie_account, amount); + let transfer_from_result = client + .call(&ink_e2e::charlie(), &transfer_from) + .submit() + .await; + + assert!( + transfer_from_result.is_err(), + "unapproved transfer_from should fail" + ); + + // Bob approves Charlie to transfer up to amount on his behalf + let approved_value = 1_000u128; + let approve_call = call.approve(charlie_account, approved_value); + client + .call(&ink_e2e::bob(), &approve_call) + .submit() + .await + .expect("approve failed"); + + // `transfer_from` the approved amount + let transfer_from = + call.transfer_from(bob_account, charlie_account, approved_value); + let transfer_from_result = client + .call(&ink_e2e::charlie(), &transfer_from) + .submit() + .await; + assert!( + transfer_from_result.is_ok(), + "approved transfer_from should succeed" + ); + + let balance_of = call.balance_of(bob_account); + let balance_of_res = client + .call(&ink_e2e::alice(), &balance_of) + .dry_run() + .await?; + + // `transfer_from` again, this time exceeding the approved amount + let transfer_from = call.transfer_from(bob_account, charlie_account, 1); + let transfer_from_result = client + .call(&ink_e2e::charlie(), &transfer_from) + .submit() + .await; + assert!( + transfer_from_result.is_err(), + "transfer_from exceeding the approved amount should fail" + ); + + assert_eq!( + total_supply - approved_value, + balance_of_res.return_value(), + "balance_of" + ); + + Ok(()) + } + } +} diff --git a/integration-tests/erc721/.gitignore b/integration-tests/erc721/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/erc721/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/erc721/Cargo.toml b/integration-tests/erc721/Cargo.toml new file mode 100644 index 00000000000..3895e12a470 --- /dev/null +++ b/integration-tests/erc721/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "erc721" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/erc721/lib.rs b/integration-tests/erc721/lib.rs new file mode 100644 index 00000000000..0bc5cddb034 --- /dev/null +++ b/integration-tests/erc721/lib.rs @@ -0,0 +1,637 @@ +//! # ERC-721 +//! +//! This is an ERC-721 Token implementation. +//! +//! ## Warning +//! +//! This contract is an *example*. It is neither audited nor endorsed for production use. +//! Do **not** rely on it to keep anything of value secure. +//! +//! ## Overview +//! +//! This contract demonstrates how to build non-fungible or unique tokens using ink!. +//! +//! ## Error Handling +//! +//! Any function that modifies the state returns a `Result` type and does not changes the +//! state if the `Error` occurs. +//! The errors are defined as an `enum` type. Any other error or invariant violation +//! triggers a panic and therefore rolls back the transaction. +//! +//! ## Token Management +//! +//! After creating a new token, the function caller becomes the owner. +//! A token can be created, transferred, or destroyed. +//! +//! Token owners can assign other accounts for transferring specific tokens on their +//! behalf. It is also possible to authorize an operator (higher rights) for another +//! account to handle tokens. +//! +//! ### Token Creation +//! +//! Token creation start by calling the `mint(&mut self, id: u32)` function. +//! The token owner becomes the function caller. The Token ID needs to be specified +//! as the argument on this function call. +//! +//! ### Token Transfer +//! +//! Transfers may be initiated by: +//! - The owner of a token +//! - The approved address of a token +//! - An authorized operator of the current owner of a token +//! +//! The token owner can transfer a token by calling the `transfer` or `transfer_from` +//! functions. An approved address can make a token transfer by calling the +//! `transfer_from` function. Operators can transfer tokens on another account's behalf or +//! can approve a token transfer for a different account. +//! +//! ### Token Removal +//! +//! Tokens can be destroyed by burning them. Only the token owner is allowed to burn a +//! token. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod erc721 { + use ink::storage::Mapping; + + /// A token ID. + pub type TokenId = u32; + + #[ink(storage)] + #[derive(Default)] + pub struct Erc721 { + /// Mapping from token to owner. + token_owner: Mapping, + /// Mapping from token to approvals users. + token_approvals: Mapping, + /// Mapping from owner to number of owned token. + owned_tokens_count: Mapping, + /// Mapping from owner to operator approvals. + operator_approvals: Mapping<(AccountId, AccountId), ()>, + } + + #[derive(Debug, PartialEq, Eq, Copy, Clone)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + NotOwner, + NotApproved, + TokenExists, + TokenNotFound, + CannotInsert, + CannotFetchValue, + NotAllowed, + } + + /// Event emitted when a token transfer occurs. + #[ink(event)] + pub struct Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + #[ink(topic)] + id: TokenId, + } + + /// Event emitted when a token approve occurs. + #[ink(event)] + pub struct Approval { + #[ink(topic)] + from: AccountId, + #[ink(topic)] + to: AccountId, + #[ink(topic)] + id: TokenId, + } + + /// Event emitted when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. + #[ink(event)] + pub struct ApprovalForAll { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + operator: AccountId, + approved: bool, + } + + impl Erc721 { + /// Creates a new ERC-721 token contract. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Returns the balance of the owner. + /// + /// This represents the amount of unique tokens the owner has. + #[ink(message)] + pub fn balance_of(&self, owner: AccountId) -> u32 { + self.balance_of_or_zero(&owner) + } + + /// Returns the owner of the token. + #[ink(message)] + pub fn owner_of(&self, id: TokenId) -> Option { + self.token_owner.get(id) + } + + /// Returns the approved account ID for this token if any. + #[ink(message)] + pub fn get_approved(&self, id: TokenId) -> Option { + self.token_approvals.get(id) + } + + /// Returns `true` if the operator is approved by the owner. + #[ink(message)] + pub fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { + self.approved_for_all(owner, operator) + } + + /// Approves or disapproves the operator for all tokens of the caller. + #[ink(message)] + pub fn set_approval_for_all( + &mut self, + to: AccountId, + approved: bool, + ) -> Result<(), Error> { + self.approve_for_all(to, approved)?; + Ok(()) + } + + /// Approves the account to transfer the specified token on behalf of the caller. + #[ink(message)] + pub fn approve(&mut self, to: AccountId, id: TokenId) -> Result<(), Error> { + self.approve_for(&to, id)?; + Ok(()) + } + + /// Transfers the token from the caller to the given destination. + #[ink(message)] + pub fn transfer( + &mut self, + destination: AccountId, + id: TokenId, + ) -> Result<(), Error> { + let caller = self.env().caller(); + self.transfer_token_from(&caller, &destination, id)?; + Ok(()) + } + + /// Transfer approved or owned token. + #[ink(message)] + pub fn transfer_from( + &mut self, + from: AccountId, + to: AccountId, + id: TokenId, + ) -> Result<(), Error> { + self.transfer_token_from(&from, &to, id)?; + Ok(()) + } + + /// Creates a new token. + #[ink(message)] + pub fn mint(&mut self, id: TokenId) -> Result<(), Error> { + let caller = self.env().caller(); + self.add_token_to(&caller, id)?; + self.env().emit_event(Transfer { + from: Some(AccountId::from([0x0; 32])), + to: Some(caller), + id, + }); + Ok(()) + } + + /// Deletes an existing token. Only the owner can burn the token. + #[ink(message)] + pub fn burn(&mut self, id: TokenId) -> Result<(), Error> { + let caller = self.env().caller(); + let Self { + token_owner, + owned_tokens_count, + .. + } = self; + + let owner = token_owner.get(id).ok_or(Error::TokenNotFound)?; + if owner != caller { + return Err(Error::NotOwner) + }; + + let count = owned_tokens_count + .get(caller) + .map(|c| c.checked_sub(1).unwrap()) + .ok_or(Error::CannotFetchValue)?; + owned_tokens_count.insert(caller, &count); + token_owner.remove(id); + + self.env().emit_event(Transfer { + from: Some(caller), + to: Some(AccountId::from([0x0; 32])), + id, + }); + + Ok(()) + } + + /// Transfers token `id` `from` the sender to the `to` `AccountId`. + fn transfer_token_from( + &mut self, + from: &AccountId, + to: &AccountId, + id: TokenId, + ) -> Result<(), Error> { + let caller = self.env().caller(); + if !self.exists(id) { + return Err(Error::TokenNotFound) + }; + if !self.approved_or_owner(Some(caller), id) { + return Err(Error::NotApproved) + }; + self.clear_approval(id); + self.remove_token_from(from, id)?; + self.add_token_to(to, id)?; + self.env().emit_event(Transfer { + from: Some(*from), + to: Some(*to), + id, + }); + Ok(()) + } + + /// Removes token `id` from the owner. + fn remove_token_from( + &mut self, + from: &AccountId, + id: TokenId, + ) -> Result<(), Error> { + let Self { + token_owner, + owned_tokens_count, + .. + } = self; + + if !token_owner.contains(id) { + return Err(Error::TokenNotFound) + } + + let count = owned_tokens_count + .get(from) + .map(|c| c.checked_sub(1).unwrap()) + .ok_or(Error::CannotFetchValue)?; + owned_tokens_count.insert(from, &count); + token_owner.remove(id); + + Ok(()) + } + + /// Adds the token `id` to the `to` AccountID. + fn add_token_to(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> { + let Self { + token_owner, + owned_tokens_count, + .. + } = self; + + if token_owner.contains(id) { + return Err(Error::TokenExists) + } + + if *to == AccountId::from([0x0; 32]) { + return Err(Error::NotAllowed) + }; + + let count = owned_tokens_count + .get(to) + .map(|c| c.checked_add(1).unwrap()) + .unwrap_or(1); + + owned_tokens_count.insert(to, &count); + token_owner.insert(id, to); + + Ok(()) + } + + /// Approves or disapproves the operator to transfer all tokens of the caller. + fn approve_for_all( + &mut self, + to: AccountId, + approved: bool, + ) -> Result<(), Error> { + let caller = self.env().caller(); + if to == caller { + return Err(Error::NotAllowed) + } + self.env().emit_event(ApprovalForAll { + owner: caller, + operator: to, + approved, + }); + + if approved { + self.operator_approvals.insert((&caller, &to), &()); + } else { + self.operator_approvals.remove((&caller, &to)); + } + + Ok(()) + } + + /// Approve the passed `AccountId` to transfer the specified token on behalf of + /// the message's sender. + fn approve_for(&mut self, to: &AccountId, id: TokenId) -> Result<(), Error> { + let caller = self.env().caller(); + let owner = self.owner_of(id); + if !(owner == Some(caller) + || self.approved_for_all(owner.expect("Error with AccountId"), caller)) + { + return Err(Error::NotAllowed) + }; + + if *to == AccountId::from([0x0; 32]) { + return Err(Error::NotAllowed) + }; + + if self.token_approvals.contains(id) { + return Err(Error::CannotInsert) + } else { + self.token_approvals.insert(id, to); + } + + self.env().emit_event(Approval { + from: caller, + to: *to, + id, + }); + + Ok(()) + } + + /// Removes existing approval from token `id`. + fn clear_approval(&mut self, id: TokenId) { + self.token_approvals.remove(id); + } + + // Returns the total number of tokens from an account. + fn balance_of_or_zero(&self, of: &AccountId) -> u32 { + self.owned_tokens_count.get(of).unwrap_or(0) + } + + /// Gets an operator on other Account's behalf. + fn approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool { + self.operator_approvals.contains((&owner, &operator)) + } + + /// Returns true if the `AccountId` `from` is the owner of token `id` + /// or it has been approved on behalf of the token `id` owner. + fn approved_or_owner(&self, from: Option, id: TokenId) -> bool { + let owner = self.owner_of(id); + from != Some(AccountId::from([0x0; 32])) + && (from == owner + || from == self.token_approvals.get(id) + || self.approved_for_all( + owner.expect("Error with AccountId"), + from.expect("Error with AccountId"), + )) + } + + /// Returns true if token `id` exists or false if it does not. + fn exists(&self, id: TokenId) -> bool { + self.token_owner.contains(id) + } + } + + /// Unit tests + #[cfg(test)] + mod tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + + #[ink::test] + fn mint_works() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Token 1 does not exists. + assert_eq!(erc721.owner_of(1), None); + // Alice does not owns tokens. + assert_eq!(erc721.balance_of(accounts.alice), 0); + // Create token Id 1. + assert_eq!(erc721.mint(1), Ok(())); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + } + + #[ink::test] + fn mint_existing_should_fail() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1. + assert_eq!(erc721.mint(1), Ok(())); + // The first Transfer event takes place + assert_eq!(1, ink::env::test::recorded_events().count()); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Alice owns token Id 1. + assert_eq!(erc721.owner_of(1), Some(accounts.alice)); + // Cannot create token Id if it exists. + // Bob cannot own token Id 1. + assert_eq!(erc721.mint(1), Err(Error::TokenExists)); + } + + #[ink::test] + fn transfer_works() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1 for Alice + assert_eq!(erc721.mint(1), Ok(())); + // Alice owns token 1 + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Bob does not owns any token + assert_eq!(erc721.balance_of(accounts.bob), 0); + // The first Transfer event takes place + assert_eq!(1, ink::env::test::recorded_events().count()); + // Alice transfers token 1 to Bob + assert_eq!(erc721.transfer(accounts.bob, 1), Ok(())); + // The second Transfer event takes place + assert_eq!(2, ink::env::test::recorded_events().count()); + // Bob owns token 1 + assert_eq!(erc721.balance_of(accounts.bob), 1); + } + + #[ink::test] + fn invalid_transfer_should_fail() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Transfer token fails if it does not exists. + assert_eq!(erc721.transfer(accounts.bob, 2), Err(Error::TokenNotFound)); + // Token Id 2 does not exists. + assert_eq!(erc721.owner_of(2), None); + // Create token Id 2. + assert_eq!(erc721.mint(2), Ok(())); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Token Id 2 is owned by Alice. + assert_eq!(erc721.owner_of(2), Some(accounts.alice)); + // Set Bob as caller + set_caller(accounts.bob); + // Bob cannot transfer not owned tokens. + assert_eq!(erc721.transfer(accounts.eve, 2), Err(Error::NotApproved)); + } + + #[ink::test] + fn approved_transfer_works() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1. + assert_eq!(erc721.mint(1), Ok(())); + // Token Id 1 is owned by Alice. + assert_eq!(erc721.owner_of(1), Some(accounts.alice)); + // Approve token Id 1 transfer for Bob on behalf of Alice. + assert_eq!(erc721.approve(accounts.bob, 1), Ok(())); + // Set Bob as caller + set_caller(accounts.bob); + // Bob transfers token Id 1 from Alice to Eve. + assert_eq!( + erc721.transfer_from(accounts.alice, accounts.eve, 1), + Ok(()) + ); + // TokenId 3 is owned by Eve. + assert_eq!(erc721.owner_of(1), Some(accounts.eve)); + // Alice does not owns tokens. + assert_eq!(erc721.balance_of(accounts.alice), 0); + // Bob does not owns tokens. + assert_eq!(erc721.balance_of(accounts.bob), 0); + // Eve owns 1 token. + assert_eq!(erc721.balance_of(accounts.eve), 1); + } + + #[ink::test] + fn approved_for_all_works() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1. + assert_eq!(erc721.mint(1), Ok(())); + // Create token Id 2. + assert_eq!(erc721.mint(2), Ok(())); + // Alice owns 2 tokens. + assert_eq!(erc721.balance_of(accounts.alice), 2); + // Approve token Id 1 transfer for Bob on behalf of Alice. + assert_eq!(erc721.set_approval_for_all(accounts.bob, true), Ok(())); + // Bob is an approved operator for Alice + assert!(erc721.is_approved_for_all(accounts.alice, accounts.bob)); + // Set Bob as caller + set_caller(accounts.bob); + // Bob transfers token Id 1 from Alice to Eve. + assert_eq!( + erc721.transfer_from(accounts.alice, accounts.eve, 1), + Ok(()) + ); + // TokenId 1 is owned by Eve. + assert_eq!(erc721.owner_of(1), Some(accounts.eve)); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Bob transfers token Id 2 from Alice to Eve. + assert_eq!( + erc721.transfer_from(accounts.alice, accounts.eve, 2), + Ok(()) + ); + // Bob does not own tokens. + assert_eq!(erc721.balance_of(accounts.bob), 0); + // Eve owns 2 tokens. + assert_eq!(erc721.balance_of(accounts.eve), 2); + // Remove operator approval for Bob on behalf of Alice. + set_caller(accounts.alice); + assert_eq!(erc721.set_approval_for_all(accounts.bob, false), Ok(())); + // Bob is not an approved operator for Alice. + assert!(!erc721.is_approved_for_all(accounts.alice, accounts.bob)); + } + + #[ink::test] + fn not_approved_transfer_should_fail() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1. + assert_eq!(erc721.mint(1), Ok(())); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Bob does not owns tokens. + assert_eq!(erc721.balance_of(accounts.bob), 0); + // Eve does not owns tokens. + assert_eq!(erc721.balance_of(accounts.eve), 0); + // Set Eve as caller + set_caller(accounts.eve); + // Eve is not an approved operator by Alice. + assert_eq!( + erc721.transfer_from(accounts.alice, accounts.frank, 1), + Err(Error::NotApproved) + ); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Bob does not owns tokens. + assert_eq!(erc721.balance_of(accounts.bob), 0); + // Eve does not owns tokens. + assert_eq!(erc721.balance_of(accounts.eve), 0); + } + + #[ink::test] + fn burn_works() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1 for Alice + assert_eq!(erc721.mint(1), Ok(())); + // Alice owns 1 token. + assert_eq!(erc721.balance_of(accounts.alice), 1); + // Alice owns token Id 1. + assert_eq!(erc721.owner_of(1), Some(accounts.alice)); + // Destroy token Id 1. + assert_eq!(erc721.burn(1), Ok(())); + // Alice does not owns tokens. + assert_eq!(erc721.balance_of(accounts.alice), 0); + // Token Id 1 does not exists + assert_eq!(erc721.owner_of(1), None); + } + + #[ink::test] + fn burn_fails_token_not_found() { + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Try burning a non existent token + assert_eq!(erc721.burn(1), Err(Error::TokenNotFound)); + } + + #[ink::test] + fn burn_fails_not_owner() { + let accounts = + ink::env::test::default_accounts::(); + // Create a new contract instance. + let mut erc721 = Erc721::new(); + // Create token Id 1 for Alice + assert_eq!(erc721.mint(1), Ok(())); + // Try burning this token with a different account + set_caller(accounts.eve); + assert_eq!(erc721.burn(1), Err(Error::NotOwner)); + } + + fn set_caller(sender: AccountId) { + ink::env::test::set_caller::(sender); + } + } +} diff --git a/integration-tests/events/.gitignore b/integration-tests/events/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/events/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/events/Cargo.toml b/integration-tests/events/Cargo.toml new file mode 100644 index 00000000000..ebc1e837151 --- /dev/null +++ b/integration-tests/events/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "events" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +event-def = { path = "event-def", default-features = false } +event-def2 = { path = "event-def2", default-features = false } +event-def-unused = { path = "event-def-unused", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "event-def/std", + "event-def2/std", + "event-def-unused/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[profile.test] +# Need this for linkme crate to work for the event metadata unit test. +# See https://github.com/dtolnay/linkme/issues/61#issuecomment-1503653702 +lto = "thin" diff --git a/integration-tests/events/event-def-unused/Cargo.toml b/integration-tests/events/event-def-unused/Cargo.toml new file mode 100644 index 00000000000..1bdfc90af33 --- /dev/null +++ b/integration-tests/events/event-def-unused/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "event-def-unused" +version = "0.1.0" +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[features] +default = ["std"] +std = [ + "ink/std", +] diff --git a/integration-tests/events/event-def-unused/src/lib.rs b/integration-tests/events/event-def-unused/src/lib.rs new file mode 100644 index 00000000000..cbf2d719fd8 --- /dev/null +++ b/integration-tests/events/event-def-unused/src/lib.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::trait_definition] +pub trait FlipperTrait { + #[ink(message)] + fn flip(&mut self); +} + +#[ink::event] +pub struct EventDefUnused { + #[ink(topic)] + pub hash: [u8; 32], + #[ink(topic)] + pub maybe_hash: Option<[u8; 32]>, +} diff --git a/integration-tests/events/event-def/Cargo.toml b/integration-tests/events/event-def/Cargo.toml new file mode 100644 index 00000000000..902d532e8ff --- /dev/null +++ b/integration-tests/events/event-def/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "event-def" +version = "0.1.0" +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[features] +default = ["std"] +std = [ + "ink/std", +] diff --git a/integration-tests/events/event-def/src/lib.rs b/integration-tests/events/event-def/src/lib.rs new file mode 100644 index 00000000000..4f9e8ffac10 --- /dev/null +++ b/integration-tests/events/event-def/src/lib.rs @@ -0,0 +1,14 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::event] +pub struct ForeignFlipped { + pub value: bool, +} + +#[ink::event] +pub struct ThirtyTwoByteTopics { + #[ink(topic)] + pub hash: [u8; 32], + #[ink(topic)] + pub maybe_hash: Option<[u8; 32]>, +} diff --git a/integration-tests/events/event-def2/Cargo.toml b/integration-tests/events/event-def2/Cargo.toml new file mode 100644 index 00000000000..18bc859173d --- /dev/null +++ b/integration-tests/events/event-def2/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "event-def2" +version = "0.1.0" +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[features] +default = ["std"] +std = [ + "ink/std", +] diff --git a/integration-tests/events/event-def2/src/lib.rs b/integration-tests/events/event-def2/src/lib.rs new file mode 100644 index 00000000000..f27050f895d --- /dev/null +++ b/integration-tests/events/event-def2/src/lib.rs @@ -0,0 +1,9 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::event] +pub struct EventDefAnotherCrate { + #[ink(topic)] + pub hash: [u8; 32], + #[ink(topic)] + pub maybe_hash: Option<[u8; 32]>, +} diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs new file mode 100644 index 00000000000..2fa51eafdcf --- /dev/null +++ b/integration-tests/events/lib.rs @@ -0,0 +1,415 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::event(anonymous = true)] +pub struct AnonymousEvent { + #[ink(topic)] + pub topic: [u8; 32], + pub field_1: u32, +} + +#[ink::contract] +pub mod events { + #[ink(storage)] + pub struct Events { + value: bool, + } + + #[ink(event)] + pub struct InlineFlipped { + value: bool, + } + + #[ink( + event, + signature_topic = "1111111111111111111111111111111111111111111111111111111111111111" + )] + pub struct InlineCustomFlipped { + value: bool, + } + + #[ink(event)] + #[ink(anonymous)] + pub struct InlineAnonymousEvent { + #[ink(topic)] + pub topic: [u8; 32], + pub field_1: u32, + } + + impl Events { + /// Creates a new events smart contract initialized with the given value. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + /// Flips the current value of the boolean. + #[ink(message)] + pub fn flip_with_foreign_event(&mut self) { + self.value = !self.value; + self.env() + .emit_event(event_def::ForeignFlipped { value: self.value }) + } + + /// Flips the current value of the boolean. + #[ink(message)] + pub fn flip_with_inline_event(&mut self) { + self.value = !self.value; + self.env().emit_event(InlineFlipped { value: self.value }) + } + + /// Flips the current value of the boolean. + #[ink(message)] + pub fn flip_with_inline_custom_event(&mut self) { + self.value = !self.value; + self.env() + .emit_event(InlineCustomFlipped { value: self.value }) + } + + /// Emit an event with a 32 byte topic. + #[ink(message)] + pub fn emit_32_byte_topic_event(&self, maybe_hash: Option<[u8; 32]>) { + self.env().emit_event(event_def::ThirtyTwoByteTopics { + hash: [0x42; 32], + maybe_hash, + }) + } + + /// Emit an event from a different crate. + #[ink(message)] + pub fn emit_event_from_a_different_crate(&self, maybe_hash: Option<[u8; 32]>) { + self.env().emit_event(event_def2::EventDefAnotherCrate { + hash: [0x42; 32], + maybe_hash, + }) + } + + /// Emit a inline and standalone anonymous events + #[ink(message)] + pub fn emit_anonymous_events(&self, topic: [u8; 32]) { + self.env() + .emit_event(InlineAnonymousEvent { topic, field_1: 42 }); + self.env() + .emit_event(super::AnonymousEvent { topic, field_1: 42 }); + } + } + + /// Implementing the trait from the `event_def_unused` crate includes all defined + /// events there. + impl event_def_unused::FlipperTrait for Events { + #[ink(message)] + fn flip(&mut self) { + self.value = !self.value; + } + } + + #[cfg(test)] + mod tests { + use super::*; + use ink::scale::Decode as _; + + #[test] + fn collects_specs_for_all_linked_and_used_events() { + let event_specs = ink::metadata::collect_events(); + assert_eq!(8, event_specs.len()); + + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"ForeignFlipped")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"InlineFlipped")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"InlineCustomFlipped")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"ThirtyTwoByteTopics")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"EventDefAnotherCrate")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"AnonymousEvent")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"InlineAnonymousEvent")); + + // The event is not used in the code by being included in the metadata + // because we implement trait form `event_def_unused` crate. + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"EventDefUnused")); + } + + #[ink::test] + fn it_works() { + let mut events = Events::new(false); + events.flip_with_foreign_event(); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + let event = &emitted_events[0]; + + let decoded_event = ::decode(&mut &event.data[..]) + .expect("encountered invalid contract event data buffer"); + assert!(decoded_event.value); + } + + #[ink::test] + fn option_topic_some_has_topic() { + let events = Events::new(false); + events.emit_32_byte_topic_event(Some([0xAA; 32])); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + let event = &emitted_events[0]; + + assert_eq!(event.topics.len(), 3); + let signature_topic = + ::SIGNATURE_TOPIC + .map(|topic| topic.to_vec()); + assert_eq!(Some(&event.topics[0]), signature_topic.as_ref()); + assert_eq!(event.topics[1], [0x42; 32]); + assert_eq!( + event.topics[2], [0xAA; 32], + "option topic should be published" + ); + } + + #[ink::test] + fn option_topic_none_encoded_as_0() { + let events = Events::new(false); + events.emit_32_byte_topic_event(None); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + let event = &emitted_events[0]; + + let signature_topic = + ::SIGNATURE_TOPIC + .map(|topic| topic.to_vec()) + .unwrap(); + + let expected_topics = vec![ + signature_topic, + [0x42; 32].to_vec(), + [0x00; 32].to_vec(), // None is encoded as 0x00 + ]; + assert_eq!(expected_topics, event.topics); + } + + #[ink::test] + fn custom_signature_topic() { + let mut events = Events::new(false); + events.flip_with_inline_custom_event(); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + + let signature_topic = + ::SIGNATURE_TOPIC; + + assert_eq!(Some([17u8; 32]), signature_topic); + } + + #[ink::test] + fn anonymous_events_emit_no_signature_topics() { + let events = Events::new(false); + let topic = [0x42; 32]; + events.emit_anonymous_events(topic); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(2, emitted_events.len()); + + let event = &emitted_events[0]; + assert_eq!(event.topics.len(), 1); + assert_eq!(event.topics[0], topic); + + let event = &emitted_events[1]; + assert_eq!(event.topics.len(), 1); + assert_eq!(event.topics[0], topic); + + let signature_topic = + ::SIGNATURE_TOPIC; + assert_eq!(None, signature_topic); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + ContractsBackend, + H256, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn emits_foreign_event( + mut client: Client, + ) -> E2EResult<()> { + // given + let init_value = false; + let mut constructor = EventsRef::new(init_value); + let contract = client + .instantiate("events", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let flip = call.flip_with_foreign_event(); + let flip_res = client + .call(&ink_e2e::bob(), &flip) + .submit() + .await + .expect("flip failed"); + + let contract_events = flip_res.contract_emitted_events()?; + + // then + assert_eq!(1, contract_events.len()); + let contract_event = &contract_events[0]; + let flipped: event_def::ForeignFlipped = + ink::scale::Decode::decode(&mut &contract_event.event.data[..]) + .expect("encountered invalid contract event data buffer"); + assert_eq!(!init_value, flipped.value); + + let signature_topic = + ::SIGNATURE_TOPIC + .map(H256::from) + .unwrap(); + + let expected_topics = vec![signature_topic]; + assert_eq!(expected_topics, contract_event.topics); + + Ok(()) + } + + #[ink_e2e::test] + async fn emits_inline_event( + mut client: Client, + ) -> E2EResult<()> { + // given + let init_value = false; + let mut constructor = EventsRef::new(init_value); + let contract = client + .instantiate("events", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let flip = call.flip_with_inline_event(); + let flip_res = client + .call(&ink_e2e::bob(), &flip) + .submit() + .await + .expect("flip failed"); + + let contract_events = flip_res.contract_emitted_events()?; + + // then + assert_eq!(1, contract_events.len()); + let contract_event = &contract_events[0]; + let flipped: InlineFlipped = + ink::scale::Decode::decode(&mut &contract_event.event.data[..]) + .expect("encountered invalid contract event data buffer"); + assert_eq!(!init_value, flipped.value); + + let signature_topic = ::SIGNATURE_TOPIC + .map(H256::from) + .unwrap(); + + let expected_topics = vec![signature_topic]; + assert_eq!(expected_topics, contract_event.topics); + + Ok(()) + } + + #[ink_e2e::test] + async fn emits_event_with_option_topic_none( + mut client: Client, + ) -> E2EResult<()> { + // given + let init_value = false; + let mut constructor = EventsRef::new(init_value); + let contract = client + .instantiate("events", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // when + let call = call.emit_32_byte_topic_event(None); + let call_res = client + .call(&ink_e2e::bob(), &call) + .submit() + .await + .expect("emit_32_byte_topic_event failed"); + + let contract_events = call_res.contract_emitted_events()?; + + // then + assert_eq!(1, contract_events.len()); + let contract_event = &contract_events[0]; + let event: event_def::ThirtyTwoByteTopics = + ink::scale::Decode::decode(&mut &contract_event.event.data[..]) + .expect("encountered invalid contract event data buffer"); + assert!(event.maybe_hash.is_none()); + + let signature_topic = + ::SIGNATURE_TOPIC + .map(H256::from) + .unwrap(); + + let expected_topics = vec![ + signature_topic, + [0x42; 32].into(), + [0x00; 32].into(), // None is encoded as 0x00 + ]; + assert_eq!(expected_topics, contract_event.topics); + + Ok(()) + } + + #[ink_e2e::test] + async fn emits_custom_signature_event( + mut client: Client, + ) -> E2EResult<()> { + // given + let init_value = false; + let mut constructor = EventsRef::new(init_value); + let contract = client + .instantiate("events", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let call = call.flip_with_inline_custom_event(); + let call_res = client + .call(&ink_e2e::bob(), &call) + .submit() + .await + .expect("flip_with_inline_custom_event failed"); + + let contract_events = call_res.contract_emitted_events()?; + + // then + assert_eq!(1, contract_events.len()); + + let signature_topic = + ::SIGNATURE_TOPIC; + + assert_eq!(Some([17u8; 32]), signature_topic); + + Ok(()) + } + } +} diff --git a/integration-tests/incrementer/.gitignore b/integration-tests/incrementer/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/incrementer/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/incrementer/Cargo.toml b/integration-tests/incrementer/Cargo.toml new file mode 100644 index 00000000000..8080f0be9fe --- /dev/null +++ b/integration-tests/incrementer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "incrementer" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/incrementer/lib.rs b/integration-tests/incrementer/lib.rs new file mode 100644 index 00000000000..5163ecd4a3b --- /dev/null +++ b/integration-tests/incrementer/lib.rs @@ -0,0 +1,57 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::incrementer::{ + Incrementer, + IncrementerRef, +}; + +#[ink::contract] +mod incrementer { + #[ink(storage)] + pub struct Incrementer { + value: i32, + } + + impl Incrementer { + #[ink(constructor)] + pub fn new(init_value: i32) -> Self { + Self { value: init_value } + } + + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + #[ink(message)] + pub fn inc(&mut self, by: i32) { + self.value = self.value.checked_add(by).unwrap(); + } + + #[ink(message)] + pub fn get(&self) -> i32 { + self.value + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + let contract = Incrementer::new_default(); + assert_eq!(contract.get(), 0); + } + + #[ink::test] + fn it_works() { + let mut contract = Incrementer::new(42); + assert_eq!(contract.get(), 42); + contract.inc(5); + assert_eq!(contract.get(), 47); + contract.inc(-50); + assert_eq!(contract.get(), -3); + } + } +} diff --git a/integration-tests/lang-err-integration-tests/.gitignore b/integration-tests/lang-err-integration-tests/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/lang-err-integration-tests/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml b/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml new file mode 100755 index 00000000000..b311b634e65 --- /dev/null +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "call_builder_delegate" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +incrementer = { path = "../../incrementer", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "incrementer/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs new file mode 100755 index 00000000000..af94928e65e --- /dev/null +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs @@ -0,0 +1,185 @@ +//! # Integration Tests for `LangError` +//! +//! This contract is used to ensure that the behavior around `LangError`s works as +//! expected. +//! +//! In particular, it exercises the codepaths that stem from the usage of the +//! [`CallBuilder`](`ink::env::call::CallBuilder`) and +//! [`CreateBuilder`](`ink::env::call::CreateBuilder`) structs. +//! +//! This differs from the codepath used by external tooling, such as `cargo-contract` or +//! the `Contracts-UI` which instead depend on methods from the Contracts pallet which are +//! exposed via RPC. +//! +//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure +//! that you have a node which includes the Contracts pallet running alongside your tests. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod call_builder { + use ink::env::{ + call::{ + build_call, + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }; + + #[ink(storage)] + #[derive(Default)] + pub struct CallBuilderDelegateTest { + /// Since we're going to `DelegateCall` into the `incrementer` contract, we need + /// to make sure our storage layout matches. + value: i32, + } + + impl CallBuilderDelegateTest { + #[ink(constructor)] + pub fn new(value: i32) -> Self { + Self { value } + } + + /// Call a contract using the `CallBuilder`. + /// + /// Since we can't use the `CallBuilder` in a test environment directly we need + /// this wrapper to test things like crafting calls with invalid + /// selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` + /// directly from a contract message without erroring out ourselves. + #[ink(message)] + pub fn delegate( + &mut self, + code_hash: Hash, + selector: [u8; 4], + ) -> Option { + let result = build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke() + .expect("Error from the Contracts pallet."); + + match result { + Ok(_) => None, + Err(e @ ink::LangError::CouldNotReadInput) => Some(e), + Err(_) => { + unimplemented!("No other `LangError` variants exist at the moment.") + } + } + } + + /// Call a contract using the `CallBuilder`. + /// + /// Since we can't use the `CallBuilder` in a test environment directly we need + /// this wrapper to test things like crafting calls with invalid + /// selectors. + /// + /// This message does not allow the caller to handle any `LangErrors`, for that + /// use the `call` message instead. + #[ink(message)] + pub fn invoke(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { + use ink::env::call::build_call; + + build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .invoke() + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + ChainBackend, + ContractsBackend, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_invalid_message_selector_can_be_handled( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderDelegateTestRef::new(Default::default()); + let call_builder_contract = client + .instantiate("call_builder_delegate", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = + call_builder_contract.call::(); + + let code_hash = client + .upload("incrementer", &origin) + .submit() + .await + .expect("upload `incrementer` failed") + .code_hash; + + let selector = ink::selector_bytes!("invalid_selector"); + let call = call_builder_call.delegate(code_hash, selector); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Calling `call_builder::delegate` failed"); + + assert!(matches!( + call_result.return_value(), + Some(ink::LangError::CouldNotReadInput) + )); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_invalid_message_selector_panics_on_invoke( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::charlie(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderDelegateTestRef::new(Default::default()); + let call_builder_contract = client + .instantiate("call_builder_delegate", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = + call_builder_contract.call::(); + + let code_hash = client + .upload("incrementer", &origin) + .submit() + .await + .expect("upload `incrementer` failed") + .code_hash; + + // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method + // we expect this to panic. + let selector = ink::selector_bytes!("invalid_selector"); + let call = call_builder_call.invoke(code_hash, selector); + let call_result = client.call(&origin, &call).dry_run().await; + + if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { + assert!(dry_run + .debug_message + .contains("Cross-contract call failed with CouldNotReadInput")); + } else { + panic!("Expected call to fail"); + } + + Ok(()) + } + } +} diff --git a/integration-tests/lang-err-integration-tests/call-builder/Cargo.toml b/integration-tests/lang-err-integration-tests/call-builder/Cargo.toml new file mode 100755 index 00000000000..823cb5e1ed6 --- /dev/null +++ b/integration-tests/lang-err-integration-tests/call-builder/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "call_builder" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +constructors_return_value = { path = "../constructors-return-value", default-features = false, features = ["ink-as-dependency"] } +integration_flipper = { path = "../integration-flipper", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "constructors_return_value/std", + "integration_flipper/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/call-builder/lib.rs b/integration-tests/lang-err-integration-tests/call-builder/lib.rs new file mode 100755 index 00000000000..d924b5829b7 --- /dev/null +++ b/integration-tests/lang-err-integration-tests/call-builder/lib.rs @@ -0,0 +1,587 @@ +//! # Integration Tests for `LangError` +//! +//! This contract is used to ensure that the behavior around `LangError`s works as +//! expected. +//! +//! In particular, it exercises the codepaths that stem from the usage of the +//! [`CallBuilder`](`ink::env::call::CallBuilder`) and +//! [`CreateBuilder`](`ink::env::call::CreateBuilder`) structs. +//! +//! This differs from the codepath used by external tooling, such as `cargo-contract` or +//! the `Contracts-UI` which instead depend on methods from the Contracts pallet which are +//! exposed via RPC. +//! +//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure +//! that you have a node which includes the Contracts pallet running alongside your tests. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod call_builder { + use constructors_return_value::ConstructorsReturnValueRef; + use ink::env::{ + call::{ + build_call, + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }; + + #[ink(storage)] + #[derive(Default)] + pub struct CallBuilderTest {} + + impl CallBuilderTest { + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Call a contract using the `CallBuilder`. + /// + /// Since we can't use the `CallBuilder` in a test environment directly we need + /// this wrapper to test things like crafting calls with invalid + /// selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` + /// directly from a contract message without erroring out ourselves. + #[ink(message)] + pub fn call( + &mut self, + address: AccountId, + selector: [u8; 4], + ) -> Option { + let result = build_call::() + .call(address) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::<()>() + .try_invoke() + .expect("Error from the Contracts pallet."); + + match result { + Ok(_) => None, + Err(e @ ink::LangError::CouldNotReadInput) => Some(e), + Err(_) => { + unimplemented!("No other `LangError` variants exist at the moment.") + } + } + } + + /// Call a contract using the `CallBuilder`. + /// + /// Since we can't use the `CallBuilder` in a test environment directly we need + /// this wrapper to test things like crafting calls with invalid + /// selectors. + /// + /// This message does not allow the caller to handle any `LangErrors`, for that + /// use the `call` message instead. + #[ink(message)] + pub fn invoke(&mut self, address: AccountId, selector: [u8; 4]) { + use ink::env::call::build_call; + + build_call::() + .call(address) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::<()>() + .invoke() + } + + /// Instantiate a contract using the `CreateBuilder`. + /// + /// Since we can't use the `CreateBuilder` in a test environment directly we need + /// this wrapper to test things like crafting calls with invalid + /// selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` + /// directly from a contract message without erroring out ourselves. + #[ink(message)] + pub fn call_instantiate( + &mut self, + code_hash: Hash, + selector: [u8; 4], + init_value: bool, + ) -> Option { + let mut params = ConstructorsReturnValueRef::new(init_value) + .code_hash(code_hash) + .gas_limit(0) + .endowment(0) + .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) + .params(); + + params.update_selector(Selector::new(selector)); + + let result = params + .try_instantiate() + .expect("Error from the Contracts pallet."); + + match result { + Ok(_) => None, + Err(e @ ink::LangError::CouldNotReadInput) => Some(e), + Err(_) => { + unimplemented!("No other `LangError` variants exist at the moment.") + } + } + } + + /// Attempt to instantiate a contract using the `CreateBuilder`. + /// + /// Since we can't use the `CreateBuilder` in a test environment directly we need + /// this wrapper to test things like crafting calls with invalid + /// selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` + /// directly from a contract message without erroring out ourselves. + #[ink(message)] + pub fn call_instantiate_fallible( + &mut self, + code_hash: Hash, + selector: [u8; 4], + init_value: bool, + ) -> Option< + Result< + Result, + ink::LangError, + >, + > { + let mut params = ConstructorsReturnValueRef::try_new(init_value) + .code_hash(code_hash) + .gas_limit(0) + .endowment(0) + .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) + .params(); + + params.update_selector(Selector::new(selector)); + + let lang_result = params + .try_instantiate() + .expect("Error from the Contracts pallet."); + + Some(lang_result.map(|contract_result| { + contract_result.map(|inner| ink::ToAccountId::to_account_id(&inner)) + })) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + ChainBackend, + ContractsBackend, + }; + use integration_flipper::{ + Flipper, + FlipperRef, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_invalid_message_selector_can_be_handled( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder_contract = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder_contract.call::(); + + let mut flipper_constructor = FlipperRef::new_default(); + let flipper = client + .instantiate("integration_flipper", &origin, &mut flipper_constructor) + .submit() + .await + .expect("instantiate `flipper` failed"); + let flipper_call = flipper.call::(); + + let flipper_get = flipper_call.get(); + let get_call_result = client.call(&origin, &flipper_get).dry_run().await?; + let initial_value = get_call_result.return_value(); + + let selector = ink::selector_bytes!("invalid_selector"); + let call = call_builder_call.call(flipper.account_id, selector); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Calling `call_builder::call` failed"); + + let flipper_result = call_result.return_value(); + + assert!(matches!( + flipper_result, + Some(ink::LangError::CouldNotReadInput) + )); + + let flipper_get = flipper_call.get(); + let get_call_result = client.call(&origin, &flipper_get).dry_run().await?; + let flipped_value = get_call_result.return_value(); + assert!(flipped_value == initial_value); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_invalid_message_selector_panics_on_invoke( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let mut flipper_constructor = FlipperRef::new_default(); + let flipper = client + .instantiate("integration_flipper", &origin, &mut flipper_constructor) + .submit() + .await + .expect("instantiate `flipper` failed"); + + // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method + // we expect this to panic. + let invalid_selector = [0x00, 0x00, 0x00, 0x00]; + let call = call_builder_call.invoke(flipper.account_id, invalid_selector); + let call_result = client.call(&origin, &call).dry_run().await; + + if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { + assert!( + dry_run + .debug_message + .contains("Cross-contract call failed with CouldNotReadInput"), + "Call execution failed for an unexpected reason." + ); + } else { + panic!("Call execution should've failed, but didn't."); + } + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_works_with_valid_selector( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("new"); + let init_value = true; + let call = + call_builder_call.call_instantiate(code_hash, selector, init_value); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Client failed to call `call_builder::call_instantiate`.") + .return_value(); + + assert!( + call_result.is_none(), + "Call using valid selector failed, when it should've succeeded." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_fails_with_invalid_selector( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("invalid_selector"); + let init_value = true; + let call = + call_builder_call.call_instantiate(code_hash, selector, init_value); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Client failed to call `call_builder::call_instantiate`.") + .return_value(); + + assert!( + matches!(call_result, Some(ink::LangError::CouldNotReadInput)), + "Call using invalid selector succeeded, when it should've failed." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_with_infallible_revert_constructor_encodes_ok< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("revert_new"); + let init_value = false; + let call = + call_builder_call.call_instantiate(code_hash, selector, init_value); + + let call_result = client.call(&origin, &call).dry_run().await; + + if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { + assert!(dry_run + .debug_message + .contains("The callee reverted, but did not encode an error in the output buffer."), + "Call execution failed for an unexpected reason."); + } else { + panic!("Call execution should've failed, but didn't."); + } + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_can_handle_fallible_constructor_success< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_new"); + let init_value = true; + let call = call_builder_call + .call_instantiate_fallible(code_hash, selector, init_value); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Calling `call_builder::call_instantiate_fallible` failed") + .return_value(); + + assert!( + matches!(call_result, Some(Ok(_))), + "Call to falliable constructor failed, when it should have succeeded." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_can_handle_fallible_constructor_error< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_new"); + let init_value = false; + let call = call_builder_call + .call_instantiate_fallible(code_hash, selector, init_value); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect("Calling `call_builder::call_instantiate_fallible` failed") + .return_value(); + + let contract_result = call_result + .unwrap() + .expect("Dispatching `constructors_return_value::try_new` failed."); + + assert!( + matches!( + contract_result, + Err(constructors_return_value::ConstructorError) + ), + "Got an unexpected error from the contract." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_with_fallible_revert_constructor_encodes_ok< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_revert_new"); + let init_value = true; + let call = call_builder_call + .call_instantiate_fallible(code_hash, selector, init_value); + let call_result = client.call(&origin, &call).dry_run().await; + + if let Err(ink_e2e::Error::CallDryRun(dry_run)) = call_result { + assert!(dry_run + .debug_message + .contains("The callee reverted, but did not encode an error in the output buffer."), + "Call execution failed for an unexpected reason."); + } else { + panic!("Call execution should've failed, but didn't."); + } + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_create_builder_with_fallible_revert_constructor_encodes_err< + Client: E2EBackend, + >( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + + let mut constructor = CallBuilderTestRef::new(); + let call_builder = client + .instantiate("call_builder", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("constructors_return_value", &origin) + .submit() + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_revert_new"); + let init_value = false; + let call = call_builder_call + .call_instantiate_fallible(code_hash, selector, init_value); + let call_result = client + .call(&origin, &call) + .submit() + .await + .expect( + "Client failed to call `call_builder::call_instantiate_fallible`.", + ) + .return_value(); + + assert!( + matches!(call_result, Some(Err(ink::LangError::CouldNotReadInput))), + "The callee manually encoded `CouldNotReadInput` to the output buffer, we should've + gotten that back." + ); + + Ok(()) + } + } +} diff --git a/integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml b/integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml new file mode 100644 index 00000000000..f2687c0315b --- /dev/null +++ b/integration-tests/lang-err-integration-tests/constructors-return-value/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "constructors_return_value" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs b/integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs new file mode 100644 index 00000000000..76cd2bcfdcd --- /dev/null +++ b/integration-tests/lang-err-integration-tests/constructors-return-value/lib.rs @@ -0,0 +1,250 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::constructors_return_value::{ + ConstructorError, + ConstructorsReturnValue, + ConstructorsReturnValueRef, +}; + +#[ink::contract] +pub mod constructors_return_value { + #[ink(storage)] + pub struct ConstructorsReturnValue { + value: bool, + } + + #[derive(Debug)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct ConstructorError; + + impl ConstructorsReturnValue { + /// Infallible constructor + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + /// Fallible constructor + #[ink(constructor)] + pub fn try_new(succeed: bool) -> Result { + if succeed { + Ok(Self::new(true)) + } else { + Err(ConstructorError) + } + } + + /// A constructor which reverts and fills the output buffer with an erroneously + /// encoded return value. + #[ink(constructor)] + pub fn revert_new(_init_value: bool) -> Self { + ink::env::return_value::>( + ink::env::ReturnFlags::REVERT, + &Ok(AccountId::from([0u8; 32])), + ) + } + + /// A constructor which reverts and fills the output buffer with an erroneously + /// encoded return value. + #[ink(constructor)] + pub fn try_revert_new(init_value: bool) -> Result { + let value = if init_value { + Ok(Ok(AccountId::from([0u8; 32]))) + } else { + Err(ink::LangError::CouldNotReadInput) + }; + + ink::env::return_value::< + ink::ConstructorResult>, + >(ink::env::ReturnFlags::REVERT, &value) + } + + /// Returns the current value of the contract storage. + #[ink(message)] + pub fn get_value(&self) -> bool { + self.value + } + } + + #[cfg(test)] + mod tests { + use super::ConstructorsReturnValue as Contract; + use std::any::TypeId; + + #[test] + #[allow(clippy::assertions_on_constants)] + fn infallible_constructor_reflection() { + const ID: u32 = ::ink::selector_id!("new"); + + assert!( + !>::IS_RESULT, + ); + assert_eq!( + TypeId::of::< + >::Error, + >(), + TypeId::of::<&()>(), + ) + } + + #[test] + #[allow(clippy::assertions_on_constants)] + fn fallible_constructor_reflection() { + const ID: u32 = ::ink::selector_id!("try_new"); + + assert!( + >::IS_RESULT, + ); + assert_eq!( + TypeId::of::< + >::Error, + >(), + TypeId::of::(), + ) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_infallible_constructor( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = ConstructorsReturnValueRef::new(true); + let infallible_constructor_result = client + .instantiate( + "constructors_return_value", + &ink_e2e::alice(), + &mut constructor, + ) + .dry_run() + .await?; + + let decoded_result = infallible_constructor_result.constructor_result::<()>(); + assert!( + decoded_result.is_ok(), + "Constructor dispatch should have succeeded" + ); + + let mut constructor = ConstructorsReturnValueRef::new(true); + let success = client + .instantiate( + "constructors_return_value", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .is_ok(); + + assert!(success, "Contract created successfully"); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_fallible_constructor_succeed( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = ConstructorsReturnValueRef::try_new(true); + let result = client + .instantiate( + "constructors_return_value", + &ink_e2e::bob(), + &mut constructor, + ) + .dry_run() + .await?; + + let decoded_result = + result.constructor_result::>(); + + assert!( + decoded_result.is_ok(), + "Constructor dispatch should have succeeded" + ); + + assert!( + decoded_result.unwrap().is_ok(), + "Fallible constructor should have succeeded" + ); + + let mut constructor = ConstructorsReturnValueRef::try_new(true); + let contract = client + .instantiate( + "constructors_return_value", + &ink_e2e::bob(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + let get = call.get_value(); + let value = client + .call(&ink_e2e::bob(), &get) + .dry_run() + .await? + .return_value(); + + assert_eq!( + true, value, + "Contract success should write to contract storage" + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_fallible_constructor_fails( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = ConstructorsReturnValueRef::try_new(false); + + let result = client + .instantiate( + "constructors_return_value", + &ink_e2e::charlie(), + &mut constructor, + ) + .dry_run() + .await?; + + let decoded_result = + result.constructor_result::>(); + + assert!( + decoded_result.is_ok(), + "Constructor dispatch should have succeeded" + ); + + assert!( + decoded_result.unwrap().is_err(), + "Fallible constructor should have failed" + ); + + let mut constructor = ConstructorsReturnValueRef::try_new(false); + let result = client + .instantiate( + "constructors_return_value", + &ink_e2e::charlie(), + &mut constructor, + ) + .submit() + .await; + + assert!( + matches!(result, Err(ink_e2e::Error::InstantiateExtrinsic(_))), + "Constructor should fail" + ); + + Ok(()) + } + } +} diff --git a/integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml b/integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml new file mode 100755 index 00000000000..89fe2d62c42 --- /dev/null +++ b/integration-tests/lang-err-integration-tests/contract-ref/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "contract_ref" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +integration_flipper = { path = "../integration-flipper", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "integration_flipper/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/contract-ref/lib.rs b/integration-tests/lang-err-integration-tests/contract-ref/lib.rs new file mode 100755 index 00000000000..90336b1069c --- /dev/null +++ b/integration-tests/lang-err-integration-tests/contract-ref/lib.rs @@ -0,0 +1,188 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod contract_ref { + use integration_flipper::FlipperRef; + + #[ink(storage)] + pub struct ContractRef { + flipper: FlipperRef, + } + + impl ContractRef { + #[ink(constructor)] + pub fn new(version: u32, flipper_code_hash: Hash) -> Self { + let salt = version.to_le_bytes(); + let flipper = FlipperRef::new_default() + .endowment(0) + .code_hash(flipper_code_hash) + .salt_bytes(salt) + .instantiate(); + + Self { flipper } + } + + #[ink(constructor)] + pub fn try_new(version: u32, flipper_code_hash: Hash, succeed: bool) -> Self { + let salt = version.to_le_bytes(); + let flipper = FlipperRef::try_new(succeed) + .endowment(0) + .code_hash(flipper_code_hash) + .salt_bytes(salt) + .instantiate() + .unwrap_or_else(|error| { + panic!( + "Received an error from the Flipper constructor while instantiating \ + Flipper {error:?}" + ) + }); + + Self { flipper } + } + + #[ink(message)] + pub fn flip(&mut self) { + self.flipper.flip(); + } + + #[ink(message)] + pub fn flip_check(&mut self) { + self.flipper + .try_flip() + .expect("The ink! codegen should've produced a valid call."); + } + + #[ink(message)] + pub fn get(&mut self) -> bool { + self.flipper.get() + } + + #[ink(message)] + pub fn get_check(&mut self) -> bool { + self.flipper + .try_get() + .expect("The ink! codegen should've produced a valid call.") + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_ref_can_flip_correctly( + mut client: Client, + ) -> E2EResult<()> { + let flipper_hash = client + .upload("integration_flipper", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `flipper` failed") + .code_hash; + + let mut constructor = ContractRefRef::new(0, flipper_hash); + let contract_ref = client + .instantiate("contract_ref", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract_ref.call::(); + + let get_check = call.get_check(); + let get_call_result = + client.call(&ink_e2e::alice(), &get_check).dry_run().await?; + + let initial_value = get_call_result.return_value(); + + let flip_check = call.flip_check(); + let flip_call_result = client + .call(&ink_e2e::alice(), &flip_check) + .submit() + .await + .expect("Calling `flip` failed"); + assert!( + flip_call_result.message_result().is_ok(), + "Messages now return a `Result`, which should be `Ok` here." + ); + + let get_call_result = + client.call(&ink_e2e::alice(), &get_check).dry_run().await?; + let flipped_value = get_call_result.return_value(); + assert!(flipped_value != initial_value); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_fallible_ref_can_be_instantiated( + mut client: Client, + ) -> E2EResult<()> { + let flipper_hash = client + .upload("integration_flipper", &ink_e2e::bob()) + .submit() + .await + .expect("uploading `flipper` failed") + .code_hash; + + let succeed = true; + let mut constructor = ContractRefRef::try_new(0, flipper_hash, succeed); + let contract_ref = client + .instantiate("contract_ref", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract_ref.call::(); + + let get_check = call.get_check(); + let get_call_result = + client.call(&ink_e2e::bob(), &get_check).dry_run().await?; + let initial_value = get_call_result.return_value(); + + assert!(initial_value); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_fallible_ref_fails_to_be_instantiated( + mut client: Client, + ) -> E2EResult<()> { + let flipper_hash = client + .upload("integration_flipper", &ink_e2e::charlie()) + .submit() + .await + .expect("uploading `flipper` failed") + .code_hash; + + let succeed = false; + let mut constructor = ContractRefRef::try_new(0, flipper_hash, succeed); + let instantiate_result = client + .instantiate("contract_ref", &ink_e2e::charlie(), &mut constructor) + .submit() + .await; + + assert!( + instantiate_result.is_err(), + "Call execution should've failed, but didn't." + ); + + let contains_err_msg = match instantiate_result.unwrap_err() { + ink_e2e::Error::InstantiateDryRun(dry_run) => { + dry_run.debug_message.contains( + "Received an error from the Flipper constructor while instantiating Flipper FlipperError" + ) + } + _ => false, + }; + assert!( + contains_err_msg, + "Call execution failed for an unexpected reason." + ); + + Ok(()) + } + } +} diff --git a/integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml b/integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml new file mode 100644 index 00000000000..0d1d08c1ab2 --- /dev/null +++ b/integration-tests/lang-err-integration-tests/integration-flipper/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "integration_flipper" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/lang-err-integration-tests/integration-flipper/lib.rs b/integration-tests/lang-err-integration-tests/integration-flipper/lib.rs new file mode 100644 index 00000000000..496a478578f --- /dev/null +++ b/integration-tests/lang-err-integration-tests/integration-flipper/lib.rs @@ -0,0 +1,152 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::integration_flipper::{ + Flipper, + FlipperRef, +}; + +#[ink::contract] +pub mod integration_flipper { + #[ink(storage)] + pub struct Flipper { + value: bool, + } + + #[derive(Debug)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct FlipperError; + + impl Flipper { + /// Creates a new integration_flipper smart contract initialized with the given + /// value. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + /// Creates a new integration_flipper smart contract initialized to `false`. + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + /// Attempts to create a new integration_flipper smart contract initialized with + /// the given value. + #[ink(constructor)] + pub fn try_new(succeed: bool) -> Result { + if succeed { + Ok(Self::new(true)) + } else { + Err(FlipperError) + } + } + + /// Flips the current value of the Flipper's boolean. + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + + /// Flips the current value of the Flipper's boolean. + /// + /// We should see the state being reverted here, no write should occur. + #[ink(message)] + #[allow(clippy::result_unit_err)] + pub fn err_flip(&mut self) -> Result<(), ()> { + self.flip(); + Err(()) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_can_flip_correctly( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = FlipperRef::new_default(); + let flipper = client + .instantiate("integration_flipper", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("Instantiate `integration_flipper` failed"); + let mut call = flipper.call::(); + + let get = call.get(); + let initial_value = client + .call(&ink_e2e::alice(), &get) + .dry_run() + .await? + .return_value(); + + let flip = call.flip(); + let flip_call_result = client + .call(&ink_e2e::alice(), &flip) + .submit() + .await + .expect("Calling `flip` failed"); + assert!( + flip_call_result.message_result().is_ok(), + "Messages now return a `Result`, which should be `Ok` here." + ); + + let flipped_value = client + .call(&ink_e2e::alice(), &get) + .dry_run() + .await? + .return_value(); + assert!(flipped_value != initial_value); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_message_error_reverts_state( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = FlipperRef::new_default(); + let flipper = client + .instantiate("integration_flipper", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = flipper.call::(); + + let get = call.get(); + let initial_value = client + .call(&ink_e2e::bob(), &get) + .dry_run() + .await? + .return_value(); + + let err_flip = call.err_flip(); + let err_flip_call_result = + client.call(&ink_e2e::bob(), &err_flip).submit().await; + + assert!(matches!( + err_flip_call_result, + Err(ink_e2e::Error::CallExtrinsic(_)) + )); + + let flipped_value = client + .call(&ink_e2e::bob(), &get) + .dry_run() + .await? + .return_value(); + assert!(flipped_value == initial_value); + + Ok(()) + } + } +} diff --git a/integration-tests/lazyvec-integration-test/.gitignore b/integration-tests/lazyvec-integration-test/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/lazyvec-integration-test/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/lazyvec-integration-test/Cargo.toml b/integration-tests/lazyvec-integration-test/Cargo.toml new file mode 100755 index 00000000000..19cadf29860 --- /dev/null +++ b/integration-tests/lazyvec-integration-test/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "lazyvec-integration-tests" +version = "5.0.0-alpha" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/lazyvec-integration-test/lib.rs b/integration-tests/lazyvec-integration-test/lib.rs new file mode 100755 index 00000000000..2e6fa938964 --- /dev/null +++ b/integration-tests/lazyvec-integration-test/lib.rs @@ -0,0 +1,156 @@ +//! A smart contract which demonstrates functionality of `lazyvec` functions. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod lazyvec_integration_tests { + use ink::{ + prelude::vec::Vec, + storage::StorageVec, + }; + + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct Proposal { + data: Vec, + until: BlockNumber, + approvals: u32, + min_approvals: u32, + } + + impl Proposal { + fn is_finished(&self) -> bool { + self.until < ink::env::block_number::() + } + } + + #[ink(storage)] + pub struct LazyVector { + proposals: StorageVec, + } + + impl LazyVector { + #[ink(constructor, payable)] + pub fn default() -> Self { + Self { + proposals: Default::default(), + } + } + + /// Checks whether given account is allowed to vote and didn't already + /// participate. + fn is_eligible(&self, _voter: AccountId) -> bool { + // ToDo: In production, the contract would actually verify eligible voters. + // For example, a merkle proof could be an efficient way to do this. + true + } + + /// Vote to approve the current proposal. + #[ink(message)] + pub fn approve(&mut self) { + assert!(self.is_eligible(self.env().caller())); + + if let Some(mut proposal) = self.proposals.pop() { + assert!(!proposal.is_finished()); + + proposal.approvals = proposal.approvals.saturating_add(1); + self.proposals.push(&proposal); + } + } + + /// Create a new proposal. + /// + /// Returns `None` if the current proposal is not yet finished. + #[ink(message)] + pub fn create_proposal( + &mut self, + data: Vec, + duration: BlockNumber, + min_approvals: u32, + ) -> Option { + let proposal_number = match self.proposals.peek() { + Some(last) if !last.is_finished() => return None, + _ => self.proposals.len(), + }; + + self.proposals.push(&Proposal { + data, + until: self.env().block_number().saturating_add(duration.min(6000)), + min_approvals, + approvals: 0, + }); + + Some(proposal_number) + } + + #[ink(message)] + pub fn get(&self, at: u32) -> Option { + self.proposals.get(at) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn create_and_vote( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = LazyVectorRef::default(); + let contract = client + .instantiate( + "lazyvec-integration-tests", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let create = call.create_proposal(vec![0x41], 5, 1); + let _ = client + .call(&ink_e2e::alice(), &create) + .submit() + .await + .expect("Calling `create_proposal` failed"); + + let approve = call.approve(); + let _ = client + .call(&ink_e2e::alice(), &approve) + .submit() + .await + .expect("Voting failed"); + let _ = client + .call(&ink_e2e::bob(), &approve) + .submit() + .await + .expect("Voting failed"); + + // then + let value = client + .call(&ink_e2e::alice(), &create) + .dry_run() + .await + .expect("create trapped when it shouldn't") + .return_value(); + assert_eq!(value, None); + + let value = client + .call(&ink_e2e::alice(), &call.get(0)) + .dry_run() + .await + .expect("get trapped when it shouldn't") + .return_value(); + assert_eq!(value.unwrap().approvals, 2); + + Ok(()) + } + } +} diff --git a/integration-tests/mapping-integration-tests/.cargo/config.toml b/integration-tests/mapping-integration-tests/.cargo/config.toml new file mode 100644 index 00000000000..fcfc9c88880 --- /dev/null +++ b/integration-tests/mapping-integration-tests/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +# Makes testing the fallible storage methods more efficient +INK_STATIC_BUFFER_SIZE = "256" diff --git a/integration-tests/mapping-integration-tests/.gitignore b/integration-tests/mapping-integration-tests/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/mapping-integration-tests/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/mapping-integration-tests/Cargo.toml b/integration-tests/mapping-integration-tests/Cargo.toml new file mode 100755 index 00000000000..7778efa2afc --- /dev/null +++ b/integration-tests/mapping-integration-tests/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mapping-integration-tests" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/mapping-integration-tests/lib.rs b/integration-tests/mapping-integration-tests/lib.rs new file mode 100755 index 00000000000..fd5c19c6f1f --- /dev/null +++ b/integration-tests/mapping-integration-tests/lib.rs @@ -0,0 +1,418 @@ +//! A smart contract which demonstrates functionality of `Mapping` functions. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod mapping_integration_tests { + use ink::{ + prelude::{ + string::String, + vec::Vec, + }, + storage::Mapping, + }; + + #[derive(Debug, PartialEq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum ContractError { + ValueTooLarge, + } + + /// A contract for testing `Mapping` functionality. + #[ink(storage)] + #[derive(Default)] + pub struct Mappings { + /// Mapping from owner to number of owned token. + balances: Mapping, + /// Mapping from owner to aliases. + names: Mapping>, + } + + impl Mappings { + /// Demonstrates the usage of `Mappings::default()` + /// + /// Creates an empty mapping between accounts and balances. + #[ink(constructor)] + pub fn new() -> Self { + let balances = Mapping::default(); + let names = Mapping::default(); + Self { balances, names } + } + + /// Demonstrates the usage of `Mapping::get()`. + /// + /// Returns the balance of a account, or `None` if the account is not in the + /// `Mapping`. + #[ink(message)] + pub fn get_balance(&self) -> Option { + let caller = Self::env().caller(); + self.balances.get(caller) + } + + /// Demonstrates the usage of `Mappings::insert()`. + /// + /// Assigns the value to a given account. + /// + /// Returns the size of the pre-existing balance at the specified key if any. + /// Returns `None` if the account was not previously in the `Mapping`. + #[ink(message)] + pub fn insert_balance(&mut self, value: Balance) -> Option { + let caller = Self::env().caller(); + self.balances.insert(caller, &value) + } + + /// Demonstrates the usage of `Mappings::size()`. + /// + /// Returns the size of the pre-existing balance at the specified key if any. + /// Returns `None` if the account was not previously in the `Mapping`. + #[ink(message)] + pub fn size_balance(&mut self) -> Option { + let caller = Self::env().caller(); + self.balances.size(caller) + } + + /// Demonstrates the usage of `Mapping::contains()`. + /// + /// Returns `true` if the account has any balance assigned to it. + #[ink(message)] + pub fn contains_balance(&self) -> bool { + let caller = Self::env().caller(); + self.balances.contains(caller) + } + + /// Demonstrates the usage of `Mappings::remove()`. + /// + /// Removes the balance entry for a given account. + #[ink(message)] + pub fn remove_balance(&mut self) { + let caller = Self::env().caller(); + self.balances.remove(caller); + } + + /// Demonstrates the usage of `Mappings::take()`. + /// + /// Returns the balance of a given account removing it from storage. + /// + /// Returns `None` if the account is not in the `Mapping`. + #[ink(message)] + pub fn take_balance(&mut self) -> Option { + let caller = Self::env().caller(); + self.balances.take(caller) + } + + /// Demonstrates the usage of `Mappings::try_take()` and `Mappings::try_insert()`. + /// + /// Adds a name of a given account. + /// + /// Returns `Ok(None)` if the account is not in the `Mapping`. + /// Returns `Ok(Some(_))` if the account was already in the `Mapping` + /// Returns `Err(_)` if the mapping value couldn't be encoded. + #[ink(message)] + pub fn try_insert_name(&mut self, name: String) -> Result<(), ContractError> { + let caller = Self::env().caller(); + let mut names = match self.names.try_take(caller) { + None => Vec::new(), + Some(value) => value.map_err(|_| ContractError::ValueTooLarge)?, + }; + + names.push(name); + + self.names + .try_insert(caller, &names) + .map_err(|_| ContractError::ValueTooLarge)?; + + Ok(()) + } + + /// Demonstrates the usage of `Mappings::try_get()`. + /// + /// Returns the name of a given account. + /// + /// Returns `Ok(None)` if the account is not in the `Mapping`. + /// Returns `Ok(Some(_))` if the account was already in the `Mapping` + /// Returns `Err(_)` if the mapping value couldn't be encoded. + #[ink(message)] + pub fn try_get_names(&mut self) -> Option, ContractError>> { + let caller = Self::env().caller(); + self.names + .try_get(caller) + .map(|result| result.map_err(|_| ContractError::ValueTooLarge)) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn insert_and_get_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = MappingsRef::new(); + let contract = client + .instantiate( + "mapping-integration-tests", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let insert = call.insert_balance(1_000); + let size = client + .call(&ink_e2e::alice(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + // then + let get = call.get_balance(); + let balance = client + .call(&ink_e2e::alice(), &get) + .dry_run() + .await? + .return_value(); + + assert!(size.is_none()); + assert_eq!(balance, Some(1_000)); + + Ok(()) + } + + #[ink_e2e::test] + async fn insert_and_contains_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = MappingsRef::new(); + let contract = client + .instantiate( + "mapping-integration-tests", + &ink_e2e::bob(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let insert = call.insert_balance(1_000); + let _ = client + .call(&ink_e2e::bob(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + // then + let contains = call.contains_balance(); + let is_there = client + .call(&ink_e2e::bob(), &contains) + .dry_run() + .await? + .return_value(); + + assert!(is_there); + + Ok(()) + } + + #[ink_e2e::test] + async fn reinsert_works(mut client: Client) -> E2EResult<()> { + // given + let mut constructor = MappingsRef::new(); + let contract = client + .instantiate( + "mapping-integration-tests", + &ink_e2e::charlie(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let first_insert = call.insert_balance(1_000); + let _ = client + .call(&ink_e2e::charlie(), &first_insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + let insert = call.insert_balance(10_000); + let size = client + .call(&ink_e2e::charlie(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + // then + assert!(size.is_some()); + + let get = call.get_balance(); + let balance = client + .call(&ink_e2e::charlie(), &get) + .dry_run() + .await? + .return_value(); + + assert_eq!(balance, Some(10_000)); + + Ok(()) + } + + #[ink_e2e::test] + async fn insert_and_remove_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = MappingsRef::new(); + let contract = client + .instantiate( + "mapping-integration-tests", + &ink_e2e::dave(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let insert = call.insert_balance(3_000); + let _ = client + .call(&ink_e2e::dave(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + let remove = call.remove_balance(); + let _ = client + .call(&ink_e2e::dave(), &remove) + .submit() + .await + .expect("Calling `remove_balance` failed"); + + // then + let get = call.get_balance(); + let balance = client + .call(&ink_e2e::dave(), &get) + .dry_run() + .await? + .return_value(); + + assert_eq!(balance, None); + + Ok(()) + } + + #[ink_e2e::test] + async fn insert_and_take_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = MappingsRef::new(); + let contract = client + .instantiate( + "mapping-integration-tests", + &ink_e2e::eve(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let insert = call.insert_balance(4_000); + let _ = client + .call(&ink_e2e::eve(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + let take = call.take_balance(); + let balance = client + .call(&ink_e2e::eve(), &take) + .submit() + .await + .expect("Calling `take_balance` failed") + .return_value(); + + // then + assert_eq!(balance, Some(4_000)); + + let contains = call.contains_balance(); + let is_there = client + .call(&ink_e2e::eve(), &contains) + .dry_run() + .await? + .return_value(); + + assert!(!is_there); + + Ok(()) + } + + #[ink_e2e::test] + async fn fallible_storage_methods_work( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = MappingsRef::new(); + let contract = client + .instantiate( + "mapping-integration-tests", + &ink_e2e::ferdie(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when the mapping value overgrows the buffer + let name = ink_e2e::ferdie().public_key().to_account_id().to_string(); + let insert = call.try_insert_name(name.clone()); + let mut names = Vec::new(); + while let Ok(_) = client.call(&ink_e2e::ferdie(), &insert).submit().await { + names.push(name.clone()) + } + + // then adding another one should fail gracefully + let expected_insert_result = client + .call(&ink_e2e::ferdie(), &insert) + .dry_run() + .await? + .return_value(); + let received_insert_result = + Err(crate::mapping_integration_tests::ContractError::ValueTooLarge); + assert_eq!(received_insert_result, expected_insert_result); + + // then there should be 4 entries (that's what fits into the 256kb buffer) + let received_mapping_value = client + .call(&ink_e2e::ferdie(), &call.try_get_names()) + .dry_run() + .await? + .return_value(); + let expected_mapping_value = Some(Ok(names)); + assert_eq!(received_mapping_value, expected_mapping_value); + + Ok(()) + } + } +} diff --git a/integration-tests/mother/.gitignore b/integration-tests/mother/.gitignore new file mode 100755 index 00000000000..d25982675a1 --- /dev/null +++ b/integration-tests/mother/.gitignore @@ -0,0 +1,12 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock + +*~ +#*# \ No newline at end of file diff --git a/integration-tests/mother/Cargo.toml b/integration-tests/mother/Cargo.toml new file mode 100755 index 00000000000..2c6696e9ced --- /dev/null +++ b/integration-tests/mother/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mother" +description = "Mother of all contracts" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/mother/lib.rs b/integration-tests/mother/lib.rs new file mode 100755 index 00000000000..e70c6c69e21 --- /dev/null +++ b/integration-tests/mother/lib.rs @@ -0,0 +1,251 @@ +//! # The Mother of All Contracts +//! +//! This contract is intended to make use of all features that are observable +//! by off-chain tooling (for example user interfaces). +//! It doesn't do anything useful beyond serving off-chain tooling developers +//! with a contract to test their software against. +//! +//! Currently, this includes the following: +//! +//! 1. Use complex nested input and output types. This is done with the use case of a +//! data structure needed to store data of a candle auction. +//! 2. Make contract fail with `ContractTrapped`. +//! 3. Make contract fail with returning an `Error`. +//! 4. Perform debug printing from contract into the node's log. +//! 5. Use complex types in storage. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod mother { + use ink::prelude::{ + format, + string::{ + String, + ToString, + }, + vec::Vec, + }; + + use ink::storage::{ + Mapping, + StorageVec, + }; + + /// Struct for storing winning bids per bidding sample (a block). + /// Vector index corresponds to sample number. + /// Wrapping vector, just added for testing UI components. + #[derive(Default, PartialEq, Eq, Debug, Clone)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct Bids(Vec>>); + + /// Auction outline. + #[derive(PartialEq, Eq, Debug, Clone)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Outline { + NoWinner, + WinnerDetected, + PayoutCompleted, + } + + /// Auction statuses. + /// Logic inspired by + /// [Parachain Auction](https://github.com/paritytech/polkadot/blob/master/runtime/common/src/traits.rs#L160) + #[derive(PartialEq, Eq, Debug, Clone)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Status { + /// An auction has not started yet. + NotStarted, + /// We are in the starting period of the auction, collecting initial bids. + OpeningPeriod, + /// We are in the ending period of the auction, where we are taking snapshots of + /// the winning bids. Snapshots are taken currently on per-block basis, + /// but this logic could be later evolve to take snapshots of on + /// arbitrary length (in blocks). + EndingPeriod(BlockNumber), + /// Candle was blown. + Ended(Outline), + /// We have completed the bidding process and are waiting for the Random Function + /// to return some acceptable randomness to select the winner. The number + /// represents how many blocks we have been waiting. + RfDelay(BlockNumber), + } + + /// Struct for storing auction data. + #[derive(Debug, PartialEq, Eq, Clone)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct Auction { + /// Branded name of the auction event. + name: String, + /// Some hash identifying the auction subject. + subject: Hash, + /// Structure storing the bids being made. + bids: Bids, + /// Auction terms encoded as: + /// `[start_block, opening_period, closing_period]` + terms: [BlockNumber; 3], + /// Auction status. + status: Status, + /// Candle auction can have no winner. + /// If auction is finalized, that means that the winner is determined. + finalized: bool, + /// Just a vector for the UI tests. + vector: Vec, + } + + impl Default for Auction { + fn default() -> Auction { + Auction { + name: String::default(), + subject: Hash::default(), + bids: Bids::default(), + terms: <[BlockNumber; 3]>::default(), + status: Status::OpeningPeriod, + finalized: false, + vector: >::default(), + } + } + } + + /// Way to fail a contract execution. + #[derive(Debug, Eq, PartialEq, Clone)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Failure { + Revert(String), + Panic, + } + + /// Event emitted when an auction being echoed. + #[ink(event)] + pub struct AuctionEchoed { + auction: Auction, + } + + /// Storage of the contract. + #[ink(storage)] + #[derive(Default)] + pub struct Mother { + auction: Auction, + balances: Mapping, + log: StorageVec, + } + + impl Mother { + #[ink(constructor)] + pub fn new(auction: Auction) -> Self { + Self { + balances: Default::default(), + log: Default::default(), + auction, + } + } + + #[ink(constructor)] + pub fn new_default() -> Self { + Default::default() + } + + /// Demonstrates the ability to fail a constructor safely. + #[ink(constructor)] + pub fn failed_new(fail: bool) -> Result { + if fail { + Err(Failure::Revert("Reverting instantiation".to_string())) + } else { + Ok(Default::default()) + } + } + + /// Takes an auction data struct as input and returns it back. + #[ink(message)] + pub fn echo_auction(&mut self, auction: Auction) -> Auction { + self.env().emit_event(AuctionEchoed { + auction: auction.clone(), + }); + auction + } + + /// Fails contract execution in the required way. + #[ink(message)] + pub fn revert_or_trap(&mut self, fail: Option) -> Result<(), Failure> { + match fail { + Some(Failure::Revert(_)) => { + Err(Failure::Revert("Reverting on user demand!".to_string())) + } + Some(Failure::Panic) => { + panic!("Trapping on user demand!") + } + None => Ok(()), + } + } + + /// Prints the specified string into node's debug log. + #[ink(message)] + pub fn debug_log(&mut self, _message: String) { + ink::env::debug_println!("debug_log: {}", _message); + } + + /// Mutates the input string to return "Hello, { name }" + #[ink(message)] + pub fn mut_hello_world(&self, mut message: String) -> String { + message = format!("Hello, {}", message); + message + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn echo_auction_works() { + let auction = Auction::default(); + let mut contract = Mother::new_default(); + assert_eq!(contract.echo_auction(auction.clone()), auction); + } + + #[ink::test] + fn revert_works() { + let mut contract = Mother::default(); + assert_eq!( + contract.revert_or_trap(Some(Failure::Revert( + "Testing reverting on demand!".to_string() + ))), + Err(Failure::Revert("Reverting on user demand!".to_string())) + ); + contract + .revert_or_trap(None) + .expect("Contract unexpected failure!"); + } + + #[ink::test] + fn constructor_works_or_fails() { + let contract = Mother::failed_new(true); + assert!(contract.is_err()); + assert_eq!( + contract.err(), + Some(Failure::Revert("Reverting instantiation".to_string())) + ); + + let contract = Mother::failed_new(false); + assert!(contract.is_ok()); + } + + #[ink::test] + #[should_panic] + fn trap_works() { + let mut contract = Mother::default(); + let _ = contract.revert_or_trap(Some(Failure::Panic)); + } + + #[ink::test] + fn mut_works() { + let contract = Mother::default(); + let res = contract.mut_hello_world("Alice".to_string()); + assert_eq!("Hello, Alice", res) + } + } +} diff --git a/integration-tests/multi-contract-caller/.gitignore b/integration-tests/multi-contract-caller/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/multi-contract-caller/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/multi-contract-caller/.images/code-hashes.png b/integration-tests/multi-contract-caller/.images/code-hashes.png new file mode 100644 index 0000000000000000000000000000000000000000..db938ede981c6ff3ecc18b903319e45b6e68ed82 GIT binary patch literal 95399 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 literal 0 HcmV?d00001 diff --git a/integration-tests/multi-contract-caller/Cargo.toml b/integration-tests/multi-contract-caller/Cargo.toml new file mode 100644 index 00000000000..8bcc084f248 --- /dev/null +++ b/integration-tests/multi-contract-caller/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "multi-contract-caller" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +adder = { path = "adder", default-features = false, features = ["ink-as-dependency"] } +subber = { path = "subber", default-features = false, features = ["ink-as-dependency"] } +accumulator = { path = "accumulator", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "adder/std", + "subber/std", + "accumulator/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[workspace] +members = [ + "accumulator", + "adder", + "subber", +] diff --git a/integration-tests/multi-contract-caller/README.md b/integration-tests/multi-contract-caller/README.md new file mode 100644 index 00000000000..dcde272202a --- /dev/null +++ b/integration-tests/multi-contract-caller/README.md @@ -0,0 +1,44 @@ +# `MultiContractCaller` Smart Contract + +The `multi_contract_caller` smart contract is our showcase for executing other smart +contracts on-chain. + +It consists in total of 4 different smart contract: + +- `MultiContractCaller` (root): Calls either to the Adder or Subber smart contract +- `Adder`: Increases a value in the Accumulator smart contract +- `Subber`: Decreases a value in the Accumulator smart contract +- `Accumulator`: Owns a simple `i32` value that can be incremented or decremented + +In order to test this bundle of smart contracts you need to execute the +following steps. + +You can upload the contracts using our [Contracts UI](https://contracts-ui.substrate.io/). +If you want to test it locally, our [`substrate-contracts-node`](https://use.ink/getting-started/setup/#installing-the-substrate-smart-contracts-node) +is an easy way to get a local smart contract chain running. + +1. Compile all contracts using the `./build-all.sh` script. + You will receive the respective `.contract` bundles for all the smart contracts in the `target/ink/` folder: + * `target/ink/multi_contract_caller.contract` + * `target/ink/adder/adder.contract` + * `target/ink/subber/subber.contract` + * `target/ink/accumulator/accumulator.contract` +1. Upload the `.contract` bundle of Accumulator, Adder and Subber to the chain. +1. Note down the respective code hashes of the uploaded contracts. You can + copy the contract hashes [from the page of uploaded contracts](https://contracts-ui.substrate.io/):
+ [Code Hashes Overview](https://contracts-ui.substrate.io/) +1. Instantiate the `MultiContractCaller` smart contract given all of the code hashes and a starting value. + Make sure the endowment is big enough (if you're using our `substrate-contracts-node` it's `1000000`). + The `MultiContractCaller` smart contract will take over the work of instantiating the other smart contracts for you. +1. Now you are able to run the operations provided by the `MultiContractCaller` smart contract. + Namely `get` to call to either the Adder or the Subber to either increase or decrease + the value stored in the Accumulator smart contract respectively and `switch` to switch the currently + called smart contract. + The initially called smart contract is the Adder. + +
+ + > __Note:__
+ > Depending on your Substrate version you might encounter [a bug with the pre-filled gas estimation of the UI](https://github.com/paritytech/substrate/issues/8693) + > and get the error `ExtrinsicFailed: OutOfGas`. + > As a workaround set the maximum allowed gas manually (e.g. to 5000). diff --git a/integration-tests/multi-contract-caller/accumulator/Cargo.toml b/integration-tests/multi-contract-caller/accumulator/Cargo.toml new file mode 100644 index 00000000000..e506880618d --- /dev/null +++ b/integration-tests/multi-contract-caller/accumulator/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "accumulator" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/multi-contract-caller/accumulator/lib.rs b/integration-tests/multi-contract-caller/accumulator/lib.rs new file mode 100644 index 00000000000..8bc171a0af5 --- /dev/null +++ b/integration-tests/multi-contract-caller/accumulator/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::accumulator::{ + Accumulator, + AccumulatorRef, +}; + +#[ink::contract] +pub mod accumulator { + /// Holds a simple `i32` value that can be incremented and decremented. + #[ink(storage)] + pub struct Accumulator { + value: i32, + } + + impl Accumulator { + /// Initializes the value to the initial value. + #[ink(constructor, payable)] + pub fn new(init_value: i32) -> Self { + Self { value: init_value } + } + + /// Mutates the internal value. + #[ink(message)] + pub fn inc(&mut self, by: i32) { + self.value = self.value.checked_add(by).unwrap(); + } + + /// Returns the current state. + #[ink(message)] + pub fn get(&self) -> i32 { + self.value + } + } +} diff --git a/integration-tests/multi-contract-caller/adder/Cargo.toml b/integration-tests/multi-contract-caller/adder/Cargo.toml new file mode 100644 index 00000000000..a0cb0ac47bf --- /dev/null +++ b/integration-tests/multi-contract-caller/adder/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "adder" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +accumulator = { path = "../accumulator", default-features = false, features = ["ink-as-dependency"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "accumulator/std", +] +ink-as-dependency = [] diff --git a/integration-tests/multi-contract-caller/adder/lib.rs b/integration-tests/multi-contract-caller/adder/lib.rs new file mode 100644 index 00000000000..366693122d2 --- /dev/null +++ b/integration-tests/multi-contract-caller/adder/lib.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::adder::{ + Adder, + AdderRef, +}; + +#[ink::contract] +mod adder { + use accumulator::AccumulatorRef; + + /// Increments the underlying `accumulator` value. + #[ink(storage)] + pub struct Adder { + /// The `accumulator` to store the value. + accumulator: AccumulatorRef, + } + + impl Adder { + /// Creates a new `adder` from the given `accumulator`. + #[ink(constructor, payable)] + pub fn new(accumulator: AccumulatorRef) -> Self { + Self { accumulator } + } + + /// Increases the `accumulator` value by some amount. + #[ink(message)] + pub fn inc(&mut self, by: i32) { + self.accumulator.inc(by) + } + } +} diff --git a/integration-tests/multi-contract-caller/build-all.sh b/integration-tests/multi-contract-caller/build-all.sh new file mode 100755 index 00000000000..c6de737b5f9 --- /dev/null +++ b/integration-tests/multi-contract-caller/build-all.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -eu + +cargo contract build --manifest-path accumulator/Cargo.toml +cargo contract build --manifest-path adder/Cargo.toml +cargo contract build --manifest-path subber/Cargo.toml +cargo contract build diff --git a/integration-tests/multi-contract-caller/lib.rs b/integration-tests/multi-contract-caller/lib.rs new file mode 100644 index 00000000000..1e57271f81b --- /dev/null +++ b/integration-tests/multi-contract-caller/lib.rs @@ -0,0 +1,212 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod multi_contract_caller { + use accumulator::AccumulatorRef; + use adder::AdderRef; + use subber::SubberRef; + + /// Specifies the state of the `multi_contract_caller` contract. + /// + /// In `Adder` state the `multi_contract_caller` contract will call the `Adder` + /// contract and in `Subber` state will call to the `Subber` contract. + /// + /// The initial state is `Adder`. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Which { + Adder, + Subber, + } + + /// Calls to an `adder` or `subber` contract to mutate a value in an `accumulator` + /// contract. + /// + /// # Note + /// + /// In order to instantiate the `multi_contract_caller` smart contract we first + /// have to manually put the code of the `accumulator`, `adder` + /// and `subber` smart contracts, receive their code hashes from + /// the signalled events and put their code hash into our + /// `multi_contract_caller` smart contract. + /// + /// The `AccumulatorRef`, `AdderRef` and `SubberRef` are smart contract + /// reference types that have been automatically generated by ink!. + #[ink(storage)] + pub struct MultiContractCaller { + /// Says which of `adder` or `subber` is currently in use. + which: Which, + /// The `accumulator` smart contract. + accumulator: AccumulatorRef, + /// The `adder` smart contract. + adder: AdderRef, + /// The `subber` smart contract. + subber: SubberRef, + } + + impl MultiContractCaller { + /// Instantiate a `multi_contract_caller` contract with the given sub-contract + /// codes. + #[ink(constructor, payable)] + pub fn new( + init_value: i32, + version: u32, + accumulator_code_hash: Hash, + adder_code_hash: Hash, + subber_code_hash: Hash, + ) -> Self { + let total_balance = Self::env().balance(); + let salt = version.to_le_bytes(); + let accumulator = AccumulatorRef::new(init_value) + .endowment(total_balance / 4) + .code_hash(accumulator_code_hash) + .salt_bytes(salt) + .instantiate(); + let adder = AdderRef::new(accumulator.clone()) + .endowment(total_balance / 4) + .code_hash(adder_code_hash) + .salt_bytes(salt) + .instantiate(); + let subber = SubberRef::new(accumulator.clone()) + .endowment(total_balance / 4) + .code_hash(subber_code_hash) + .salt_bytes(salt) + .instantiate(); + Self { + which: Which::Adder, + accumulator, + adder, + subber, + } + } + + /// Returns the `accumulator` value. + #[ink(message)] + pub fn get(&self) -> i32 { + self.accumulator.get() + } + + /// Delegates the call to either `Adder` or `Subber`. + #[ink(message)] + pub fn change(&mut self, by: i32) { + match self.which { + Which::Adder => self.adder.inc(by), + Which::Subber => self.subber.dec(by), + } + } + + /// Switches the `multi_contract_caller` contract. + #[ink(message)] + pub fn switch(&mut self) { + match self.which { + Which::Adder => { + self.which = Which::Subber; + } + Which::Subber => { + self.which = Which::Adder; + } + } + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_multi_contract_caller( + mut client: Client, + ) -> E2EResult<()> { + // given + let accumulator_hash = client + .upload("accumulator", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `accumulator` failed") + .code_hash; + + let adder_hash = client + .upload("adder", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `adder` failed") + .code_hash; + + let subber_hash = client + .upload("subber", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `subber` failed") + .code_hash; + + let mut constructor = MultiContractCallerRef::new( + 1234, // initial value + 1337, // salt + accumulator_hash, + adder_hash, + subber_hash, + ); + + let multi_contract_caller = client + .instantiate("multi_contract_caller", &ink_e2e::alice(), &mut constructor) + .value(10_000_000_000_000) + .submit() + .await + .expect("instantiate failed"); + let mut call = multi_contract_caller.call::(); + + // when + let get = call.get(); + let value = client + .call(&ink_e2e::bob(), &get) + .dry_run() + .await? + .return_value(); + assert_eq!(value, 1234); + let change = call.change(6); + let _ = client + .call(&ink_e2e::bob(), &change) + .submit() + .await + .expect("calling `change` failed"); + + // then + let get = call.get(); + let value = client + .call(&ink_e2e::bob(), &get) + .dry_run() + .await? + .return_value(); + assert_eq!(value, 1234 + 6); + + // when + let switch = call.switch(); + let _ = client + .call(&ink_e2e::bob(), &switch) + .submit() + .await + .expect("calling `switch` failed"); + let change = call.change(3); + let _ = client + .call(&ink_e2e::bob(), &change) + .submit() + .await + .expect("calling `change` failed"); + + // then + let get = call.get(); + let value = client + .call(&ink_e2e::bob(), &get) + .dry_run() + .await? + .return_value(); + assert_eq!(value, 1234 + 6 - 3); + + Ok(()) + } + } +} diff --git a/integration-tests/multi-contract-caller/subber/Cargo.toml b/integration-tests/multi-contract-caller/subber/Cargo.toml new file mode 100644 index 00000000000..8407a9e4cc4 --- /dev/null +++ b/integration-tests/multi-contract-caller/subber/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "subber" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +accumulator = { path = "../accumulator", default-features = false, features = ["ink-as-dependency"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + + "accumulator/std", +] +ink-as-dependency = [] diff --git a/integration-tests/multi-contract-caller/subber/lib.rs b/integration-tests/multi-contract-caller/subber/lib.rs new file mode 100644 index 00000000000..7b6e522ae0c --- /dev/null +++ b/integration-tests/multi-contract-caller/subber/lib.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::subber::{ + Subber, + SubberRef, +}; + +#[ink::contract] +mod subber { + use accumulator::AccumulatorRef; + + /// Decreases the underlying `accumulator` value. + #[ink(storage)] + pub struct Subber { + /// The `accumulator` to store the value. + accumulator: AccumulatorRef, + } + + impl Subber { + /// Creates a new `subber` from the given `accumulator`. + #[ink(constructor, payable)] + pub fn new(accumulator: AccumulatorRef) -> Self { + Self { accumulator } + } + + /// Decreases the `accumulator` value by some amount. + #[ink(message)] + pub fn dec(&mut self, by: i32) { + self.accumulator.inc(0i32.checked_sub(by).unwrap()) + } + } +} diff --git a/integration-tests/multisig/.gitignore b/integration-tests/multisig/.gitignore new file mode 100755 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/multisig/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/multisig/Cargo.toml b/integration-tests/multisig/Cargo.toml new file mode 100755 index 00000000000..d95ec880381 --- /dev/null +++ b/integration-tests/multisig/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "multisig" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/multisig/lib.rs b/integration-tests/multisig/lib.rs new file mode 100755 index 00000000000..623919891e5 --- /dev/null +++ b/integration-tests/multisig/lib.rs @@ -0,0 +1,1112 @@ +//! # Multisig Wallet +//! +//! This implements a plain multi owner wallet. +//! +//! ## Warning +//! +//! This contract is an *example*. It is neither audited nor endorsed for production use. +//! Do **not** rely on it to keep anything of value secure. +//! +//! ## Overview +//! +//! Each instantiation of this contract has a set of `owners` and a `requirement` of +//! how many of them need to agree on a `Transaction` for it to be able to be executed. +//! Every owner can submit a transaction and when enough of the other owners confirm +//! it will be able to be executed. The following invariant is enforced by the contract: +//! +//! ```ignore +//! 0 < requirement && requirement <= owners && owners <= MAX_OWNERS +//! ``` +//! +//! ## Error Handling +//! +//! With the exception of `execute_transaction` no error conditions are signalled +//! through return types. Any error or invariant violation triggers a panic and therefore +//! rolls back the transaction. +//! +//! ## Interface +//! +//! The interface is modelled after the popular Gnosis multisig wallet. However, there +//! are subtle variations from the interface. For example the `confirm_transaction` +//! will never trigger the execution of a `Transaction` even if the threshold is reached. +//! A call of `execute_transaction` is always required. This can be called by anyone. +//! +//! All the messages that are declared as only callable by the wallet must go through +//! the usual submit, confirm, execute cycle as any other transaction that should be +//! called by the wallet. For example, to add an owner you would submit a transaction +//! that calls the wallets own `add_owner` message through `submit_transaction`. +//! +//! ### Owner Management +//! +//! The messages `add_owner`, `remove_owner`, and `replace_owner` can be used to manage +//! the owner set after instantiation. +//! +//! ### Changing the Requirement +//! +//! `change_requirement` can be used to tighten or relax the `requirement` of how many +//! owner signatures are needed to execute a `Transaction`. +//! +//! ### Transaction Management +//! +//! `submit_transaction`, `cancel_transaction`, `confirm_transaction`, +//! `revoke_confirmation` and `execute_transaction` are the bread and butter messages +//! of this contract. Use them to dispatch arbitrary messages to other contracts +//! with the wallet as a sender. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::multisig::{ + ConfirmationStatus, + Multisig, + Transaction, +}; + +#[ink::contract] +mod multisig { + use ink::{ + env::{ + call::{ + build_call, + ExecutionInput, + }, + CallFlags, + }, + prelude::vec::Vec, + scale::Output, + storage::Mapping, + }; + + /// Tune this to your liking but be wary that allowing too many owners will not + /// perform well. + const MAX_OWNERS: u32 = 50; + + type TransactionId = u32; + const WRONG_TRANSACTION_ID: &str = + "The user specified an invalid transaction id. Abort."; + + /// A wrapper that allows us to encode a blob of bytes. + /// + /// We use this to pass the set of untyped (bytes) parameters to the `CallBuilder`. + #[derive(Clone)] + struct CallInput<'a>(&'a [u8]); + + impl<'a> ink::scale::Encode for CallInput<'a> { + fn encode_to(&self, dest: &mut T) { + dest.write(self.0); + } + } + + /// Indicates whether a transaction is already confirmed or needs further + /// confirmations. + #[derive(Clone, Copy)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum ConfirmationStatus { + /// The transaction is already confirmed. + Confirmed, + /// Indicates how many confirmations are remaining. + ConfirmationsNeeded(u32), + } + + /// A Transaction is what every `owner` can submit for confirmation by other owners. + /// If enough owners agree it will be executed by the contract. + #[derive(Clone)] + #[cfg_attr( + feature = "std", + derive(Debug, PartialEq, Eq, ink::storage::traits::StorageLayout) + )] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct Transaction { + /// The `AccountId` of the contract that is called in this transaction. + pub callee: AccountId, + /// The selector bytes that identifies the function of the callee that should be + /// called. + pub selector: [u8; 4], + /// The SCALE encoded parameters that are passed to the called function. + pub input: Vec, + /// The amount of chain balance that is transferred to the callee. + pub transferred_value: Balance, + /// Gas limit for the execution of the call. + pub gas_limit: u64, + /// If set to true the transaction will be allowed to re-enter the multisig + /// contract. Re-entrancy can lead to vulnerabilities. Use at your own + /// risk. + pub allow_reentry: bool, + } + + /// Errors that can occur upon calling this contract. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Returned if the call failed. + TransactionFailed, + } + + /// This is a book keeping struct that stores a list of all transaction ids and + /// also the next id to use. We need it for cleaning up the storage. + #[derive(Clone, Default)] + #[cfg_attr( + feature = "std", + derive(Debug, PartialEq, Eq, ink::storage::traits::StorageLayout) + )] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub struct Transactions { + /// Just store all transaction ids packed. + transactions: Vec, + /// We just increment this whenever a new transaction is created. + /// We never decrement or defragment. For now, the contract becomes defunct + /// when the ids are exhausted. + next_id: TransactionId, + } + + /// Emitted when an owner confirms a transaction. + #[ink(event)] + pub struct Confirmation { + /// The transaction that was confirmed. + #[ink(topic)] + transaction: TransactionId, + /// The owner that sent the confirmation. + #[ink(topic)] + from: AccountId, + /// The confirmation status after this confirmation was applied. + #[ink(topic)] + status: ConfirmationStatus, + } + + /// Emitted when an owner revoked a confirmation. + #[ink(event)] + pub struct Revocation { + /// The transaction that was revoked. + #[ink(topic)] + transaction: TransactionId, + /// The owner that sent the revocation. + #[ink(topic)] + from: AccountId, + } + + /// Emitted when an owner submits a transaction. + #[ink(event)] + pub struct Submission { + /// The transaction that was submitted. + #[ink(topic)] + transaction: TransactionId, + } + + /// Emitted when a transaction was canceled. + #[ink(event)] + pub struct Cancellation { + /// The transaction that was canceled. + #[ink(topic)] + transaction: TransactionId, + } + + /// Emitted when a transaction was executed. + #[ink(event)] + pub struct Execution { + /// The transaction that was executed. + #[ink(topic)] + transaction: TransactionId, + /// Indicates whether the transaction executed successfully. If so the `Ok` value + /// holds the output in bytes. The Option is `None` when the transaction + /// was executed through `invoke_transaction` rather than + /// `evaluate_transaction`. + #[ink(topic)] + result: Result>, Error>, + } + + /// Emitted when an owner is added to the wallet. + #[ink(event)] + pub struct OwnerAddition { + /// The owner that was added. + #[ink(topic)] + owner: AccountId, + } + + /// Emitted when an owner is removed from the wallet. + #[ink(event)] + pub struct OwnerRemoval { + /// The owner that was removed. + #[ink(topic)] + owner: AccountId, + } + + /// Emitted when the requirement changed. + #[ink(event)] + pub struct RequirementChange { + /// The new requirement value. + new_requirement: u32, + } + + #[ink(storage)] + #[derive(Default)] + pub struct Multisig { + /// Every entry in this map represents the confirmation of an owner for a + /// transaction. This is effectively a set rather than a map. + confirmations: Mapping<(TransactionId, AccountId), ()>, + /// The amount of confirmations for every transaction. This is a redundant + /// information and is kept in order to prevent iterating through the + /// confirmation set to check if a transaction is confirmed. + confirmation_count: Mapping, + /// Map the transaction id to its not-executed transaction. + transactions: Mapping, + /// We need to hold a list of all transactions so that we can clean up storage + /// when an owner is removed. + transaction_list: Transactions, + /// The list is a vector because iterating over it is necessary when cleaning + /// up the confirmation set. + owners: Vec, + /// Redundant information to speed up the check whether a caller is an owner. + is_owner: Mapping, + /// Minimum number of owners that have to confirm a transaction to be executed. + requirement: u32, + } + + impl Multisig { + /// The only constructor of the contract. + /// + /// A list of owners must be supplied and a number of how many of them must + /// confirm a transaction. Duplicate owners are silently dropped. + /// + /// # Panics + /// + /// If `requirement` violates our invariant. + #[ink(constructor)] + pub fn new(requirement: u32, mut owners: Vec) -> Self { + let mut contract = Multisig::default(); + owners.sort_unstable(); + owners.dedup(); + ensure_requirement_is_valid(owners.len() as u32, requirement); + + for owner in &owners { + contract.is_owner.insert(owner, &()); + } + + contract.owners = owners; + contract.transaction_list = Default::default(); + contract.requirement = requirement; + contract + } + + /// Add a new owner to the contract. + /// + /// Only callable by the wallet itself. + /// + /// # Panics + /// + /// If the owner already exists. + /// + /// # Examples + /// + /// Since this message must be send by the wallet itself it has to be build as a + /// `Transaction` and dispatched through `submit_transaction` and + /// `invoke_transaction`: + /// ```should_panic + /// use ink::{ + /// env::{ + /// call::{ + /// utils::ArgumentList, + /// Call, + /// CallParams, + /// ExecutionInput, + /// Selector, + /// }, + /// DefaultEnvironment as Env, + /// Environment, + /// }, + /// scale::Encode, + /// selector_bytes, + /// }; + /// use multisig::{ + /// ConfirmationStatus, + /// Transaction, + /// }; + /// + /// type AccountId = ::AccountId; + /// + /// // address of an existing `Multisig` contract + /// let wallet_id: AccountId = [7u8; 32].into(); + /// + /// // first create the transaction that adds `alice` through `add_owner` + /// let alice: AccountId = [1u8; 32].into(); + /// let add_owner_args = ArgumentList::empty().push_arg(&alice); + /// + /// let transaction_candidate = Transaction { + /// callee: wallet_id, + /// selector: selector_bytes!("add_owner"), + /// input: add_owner_args.encode(), + /// transferred_value: 0, + /// gas_limit: 0, + /// allow_reentry: true, + /// }; + /// + /// // Submit the transaction for confirmation + /// // + /// // Note that the selector bytes of the `submit_transaction` method + /// // are `[86, 244, 13, 223]`. + /// let (id, _status) = ink::env::call::build_call::() + /// .call_type(Call::new(wallet_id)) + /// .gas_limit(0) + /// .exec_input( + /// ExecutionInput::new(Selector::new([86, 244, 13, 223])) + /// .push_arg(&transaction_candidate), + /// ) + /// .returns::<(u32, ConfirmationStatus)>() + /// .invoke(); + /// + /// // Wait until all owners have confirmed and then execute the tx. + /// // + /// // Note that the selector bytes of the `invoke_transaction` method + /// // are `[185, 50, 225, 236]`. + /// ink::env::call::build_call::() + /// .call_type(Call::new(wallet_id)) + /// .gas_limit(0) + /// .exec_input(ExecutionInput::new(Selector::new([185, 50, 225, 236])).push_arg(&id)) + /// .returns::<()>() + /// .invoke(); + /// ``` + #[ink(message)] + pub fn add_owner(&mut self, new_owner: AccountId) { + self.ensure_from_wallet(); + self.ensure_no_owner(&new_owner); + ensure_requirement_is_valid( + (self.owners.len() as u32).checked_add(1).unwrap(), + self.requirement, + ); + self.is_owner.insert(new_owner, &()); + self.owners.push(new_owner); + self.env().emit_event(OwnerAddition { owner: new_owner }); + } + + /// Remove an owner from the contract. + /// + /// Only callable by the wallet itself. If by doing this the amount of owners + /// would be smaller than the requirement it is adjusted to be exactly the + /// number of owners. + /// + /// # Panics + /// + /// If `owner` is no owner of the wallet. + #[ink(message)] + pub fn remove_owner(&mut self, owner: AccountId) { + self.ensure_from_wallet(); + self.ensure_owner(&owner); + // If caller is an owner the len has to be > 0 + #[allow(clippy::arithmetic_side_effects)] + let len = self.owners.len() as u32 - 1; + let requirement = u32::min(len, self.requirement); + ensure_requirement_is_valid(len, requirement); + let owner_index = self.owner_index(&owner) as usize; + self.owners.swap_remove(owner_index); + self.is_owner.remove(owner); + self.requirement = requirement; + self.clean_owner_confirmations(&owner); + self.env().emit_event(OwnerRemoval { owner }); + } + + /// Replace an owner from the contract with a new one. + /// + /// Only callable by the wallet itself. + /// + /// # Panics + /// + /// If `old_owner` is no owner or if `new_owner` already is one. + #[ink(message)] + pub fn replace_owner(&mut self, old_owner: AccountId, new_owner: AccountId) { + self.ensure_from_wallet(); + self.ensure_owner(&old_owner); + self.ensure_no_owner(&new_owner); + let owner_index = self.owner_index(&old_owner); + self.owners[owner_index as usize] = new_owner; + self.is_owner.remove(old_owner); + self.is_owner.insert(new_owner, &()); + self.clean_owner_confirmations(&old_owner); + self.env().emit_event(OwnerRemoval { owner: old_owner }); + self.env().emit_event(OwnerAddition { owner: new_owner }); + } + + /// Change the requirement to a new value. + /// + /// Only callable by the wallet itself. + /// + /// # Panics + /// + /// If the `new_requirement` violates our invariant. + #[ink(message)] + pub fn change_requirement(&mut self, new_requirement: u32) { + self.ensure_from_wallet(); + ensure_requirement_is_valid(self.owners.len() as u32, new_requirement); + self.requirement = new_requirement; + self.env().emit_event(RequirementChange { new_requirement }); + } + + /// Add a new transaction candidate to the contract. + /// + /// This also confirms the transaction for the caller. This can be called by any + /// owner. + #[ink(message)] + pub fn submit_transaction( + &mut self, + transaction: Transaction, + ) -> (TransactionId, ConfirmationStatus) { + self.ensure_caller_is_owner(); + let trans_id = self.transaction_list.next_id; + self.transaction_list.next_id = + trans_id.checked_add(1).expect("Transaction ids exhausted."); + self.transactions.insert(trans_id, &transaction); + self.transaction_list.transactions.push(trans_id); + self.env().emit_event(Submission { + transaction: trans_id, + }); + ( + trans_id, + self.confirm_by_caller(self.env().caller(), trans_id), + ) + } + + /// Remove a transaction from the contract. + /// Only callable by the wallet itself. + /// + /// # Panics + /// + /// If `trans_id` is no valid transaction id. + #[ink(message)] + pub fn cancel_transaction(&mut self, trans_id: TransactionId) { + self.ensure_from_wallet(); + if self.take_transaction(trans_id).is_some() { + self.env().emit_event(Cancellation { + transaction: trans_id, + }); + } + } + + /// Confirm a transaction for the sender that was submitted by any owner. + /// + /// This can be called by any owner. + /// + /// # Panics + /// + /// If `trans_id` is no valid transaction id. + #[ink(message)] + pub fn confirm_transaction( + &mut self, + trans_id: TransactionId, + ) -> ConfirmationStatus { + self.ensure_caller_is_owner(); + self.ensure_transaction_exists(trans_id); + self.confirm_by_caller(self.env().caller(), trans_id) + } + + /// Revoke the senders confirmation. + /// + /// This can be called by any owner. + /// + /// # Panics + /// + /// If `trans_id` is no valid transaction id. + #[ink(message)] + pub fn revoke_confirmation(&mut self, trans_id: TransactionId) { + self.ensure_caller_is_owner(); + let caller = self.env().caller(); + if self.confirmations.contains((trans_id, caller)) { + self.confirmations.remove((trans_id, caller)); + let mut confirmation_count = self + .confirmation_count + .get(trans_id) + .expect( + "There is a entry in `self.confirmations`. Hence a count must exit.", + ); + // Will not underflow as there is at least one confirmation + #[allow(clippy::arithmetic_side_effects)] + { + confirmation_count -= 1; + } + self.confirmation_count + .insert(trans_id, &confirmation_count); + self.env().emit_event(Revocation { + transaction: trans_id, + from: caller, + }); + } + } + + /// Invoke a confirmed execution without getting its output. + /// + /// If the transaction which is invoked transfers value, this value has + /// to be sent as payment with this call. The method will fail otherwise, + /// and the transaction would then be reverted. + /// + /// Its return value indicates whether the called transaction was successful. + /// This can be called by anyone. + #[ink(message, payable)] + pub fn invoke_transaction( + &mut self, + trans_id: TransactionId, + ) -> Result<(), Error> { + self.ensure_confirmed(trans_id); + let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); + assert!(self.env().transferred_value() == t.transferred_value); + let call_flags = if t.allow_reentry { + CallFlags::ALLOW_REENTRY + } else { + CallFlags::empty() + }; + + let result = build_call::<::Env>() + .call(t.callee) + .gas_limit(t.gas_limit) + .transferred_value(t.transferred_value) + .call_flags(call_flags) + .exec_input( + ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), + ) + .returns::<()>() + .try_invoke(); + + let result = match result { + Ok(Ok(_)) => Ok(()), + _ => Err(Error::TransactionFailed), + }; + + self.env().emit_event(Execution { + transaction: trans_id, + result: result.map(|_| None), + }); + result + } + + /// Evaluate a confirmed execution and return its output as bytes. + /// + /// Its return value indicates whether the called transaction was successful and + /// contains its output when successful. + /// This can be called by anyone. + #[ink(message, payable)] + pub fn eval_transaction( + &mut self, + trans_id: TransactionId, + ) -> Result, Error> { + self.ensure_confirmed(trans_id); + let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); + let call_flags = if t.allow_reentry { + CallFlags::ALLOW_REENTRY + } else { + CallFlags::empty() + }; + + let result = build_call::<::Env>() + .call(t.callee) + .gas_limit(t.gas_limit) + .transferred_value(t.transferred_value) + .call_flags(call_flags) + .exec_input( + ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), + ) + .returns::>() + .try_invoke(); + + let result = match result { + Ok(Ok(v)) => Ok(v), + _ => Err(Error::TransactionFailed), + }; + + self.env().emit_event(Execution { + transaction: trans_id, + result: result.clone().map(Some), + }); + result + } + + /// Set the `transaction` as confirmed by `confirmer`. + /// Idempotent operation regarding an already confirmed `transaction` + /// by `confirmer`. + fn confirm_by_caller( + &mut self, + confirmer: AccountId, + transaction: TransactionId, + ) -> ConfirmationStatus { + let mut count = self.confirmation_count.get(transaction).unwrap_or(0); + let key = (transaction, confirmer); + let new_confirmation = !self.confirmations.contains(key); + if new_confirmation { + count = count.checked_add(1).unwrap(); + self.confirmations.insert(key, &()); + self.confirmation_count.insert(transaction, &count); + } + let status = { + if count >= self.requirement { + ConfirmationStatus::Confirmed + } else { + // We checked that count < self.requirement + #[allow(clippy::arithmetic_side_effects)] + ConfirmationStatus::ConfirmationsNeeded(self.requirement - count) + } + }; + if new_confirmation { + self.env().emit_event(Confirmation { + transaction, + from: confirmer, + status, + }); + } + status + } + + /// Get the index of `owner` in `self.owners`. + /// Panics if `owner` is not found in `self.owners`. + fn owner_index(&self, owner: &AccountId) -> u32 { + self.owners.iter().position(|x| *x == *owner).expect( + "This is only called after it was already verified that the id is + actually an owner.", + ) as u32 + } + + /// Remove the transaction identified by `trans_id` from `self.transactions`. + /// Also removes all confirmation state associated with it. + fn take_transaction(&mut self, trans_id: TransactionId) -> Option { + let transaction = self.transactions.get(trans_id); + if transaction.is_some() { + self.transactions.remove(trans_id); + let pos = self + .transaction_list + .transactions + .iter() + .position(|t| t == &trans_id) + .expect("The transaction exists hence it must also be in the list."); + self.transaction_list.transactions.swap_remove(pos); + for owner in self.owners.iter() { + self.confirmations.remove((trans_id, *owner)); + } + self.confirmation_count.remove(trans_id); + } + transaction + } + + /// Remove all confirmation state associated with `owner`. + /// Also adjusts the `self.confirmation_count` variable. + fn clean_owner_confirmations(&mut self, owner: &AccountId) { + for trans_id in &self.transaction_list.transactions { + let key = (*trans_id, *owner); + if self.confirmations.contains(key) { + self.confirmations.remove(key); + let mut count = self.confirmation_count.get(trans_id).unwrap_or(0); + count = count.saturating_sub(1); + self.confirmation_count.insert(trans_id, &count); + } + } + } + + /// Panic if transaction `trans_id` is not confirmed by at least + /// `self.requirement` owners. + fn ensure_confirmed(&self, trans_id: TransactionId) { + assert!( + self.confirmation_count + .get(trans_id) + .expect(WRONG_TRANSACTION_ID) + >= self.requirement + ); + } + + /// Panic if the transaction `trans_id` does not exit. + fn ensure_transaction_exists(&self, trans_id: TransactionId) { + self.transactions.get(trans_id).expect(WRONG_TRANSACTION_ID); + } + + /// Panic if the sender is no owner of the wallet. + fn ensure_caller_is_owner(&self) { + self.ensure_owner(&self.env().caller()); + } + + /// Panic if the sender is not this wallet. + fn ensure_from_wallet(&self) { + assert_eq!(self.env().caller(), self.env().account_id()); + } + + /// Panic if `owner` is not an owner, + fn ensure_owner(&self, owner: &AccountId) { + assert!(self.is_owner.contains(owner)); + } + + /// Panic if `owner` is an owner. + fn ensure_no_owner(&self, owner: &AccountId) { + assert!(!self.is_owner.contains(owner)); + } + } + + /// Panic if the number of `owners` under a `requirement` violates our + /// requirement invariant. + fn ensure_requirement_is_valid(owners: u32, requirement: u32) { + assert!(0 < requirement && requirement <= owners && owners <= MAX_OWNERS); + } + + #[cfg(test)] + mod tests { + use super::*; + use ink::env::{ + call::utils::ArgumentList, + test, + }; + + const WALLET: [u8; 32] = [7; 32]; + + impl Transaction { + fn change_requirement(requirement: u32) -> Self { + use ink::scale::Encode; + let call_args = ArgumentList::empty().push_arg(&requirement); + + // Multisig::change_requirement() + Self { + callee: AccountId::from(WALLET), + selector: ink::selector_bytes!("change_requirement"), + input: call_args.encode(), + transferred_value: 0, + gas_limit: 1000000, + allow_reentry: false, + } + } + } + + fn set_caller(sender: AccountId) { + ink::env::test::set_caller::(sender); + } + + fn set_from_wallet() { + let callee = AccountId::from(WALLET); + set_caller(callee); + } + + fn set_from_owner() { + let accounts = default_accounts(); + set_caller(accounts.alice); + } + + fn set_from_no_owner() { + let accounts = default_accounts(); + set_caller(accounts.django); + } + + fn default_accounts() -> test::DefaultAccounts { + ink::env::test::default_accounts::() + } + + fn build_contract() -> Multisig { + // Set the contract's address as `WALLET`. + let callee: AccountId = AccountId::from(WALLET); + ink::env::test::set_callee::(callee); + + let accounts = default_accounts(); + let owners = vec![accounts.alice, accounts.bob, accounts.eve]; + Multisig::new(2, owners) + } + + fn submit_transaction() -> Multisig { + let mut contract = build_contract(); + let accounts = default_accounts(); + set_from_owner(); + contract.submit_transaction(Transaction::change_requirement(1)); + assert_eq!(contract.transaction_list.transactions.len(), 1); + assert_eq!(test::recorded_events().count(), 2); + let transaction = contract.transactions.get(0).unwrap(); + assert_eq!(transaction, Transaction::change_requirement(1)); + contract.confirmations.get((0, accounts.alice)).unwrap(); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); + contract + } + + #[ink::test] + fn construction_works() { + let accounts = default_accounts(); + let owners = [accounts.alice, accounts.bob, accounts.eve]; + let contract = build_contract(); + + assert_eq!(contract.owners.len(), 3); + assert_eq!(contract.requirement, 2); + assert!(contract.owners.iter().eq(owners.iter())); + assert!(contract.is_owner.contains(accounts.alice)); + assert!(contract.is_owner.contains(accounts.bob)); + assert!(contract.is_owner.contains(accounts.eve)); + assert!(!contract.is_owner.contains(accounts.charlie)); + assert!(!contract.is_owner.contains(accounts.django)); + assert!(!contract.is_owner.contains(accounts.frank)); + assert_eq!(contract.transaction_list.transactions.len(), 0); + } + + #[ink::test] + #[should_panic] + fn empty_owner_construction_fails() { + Multisig::new(0, vec![]); + } + + #[ink::test] + #[should_panic] + fn zero_requirement_construction_fails() { + let accounts = default_accounts(); + Multisig::new(0, vec![accounts.alice, accounts.bob]); + } + + #[ink::test] + #[should_panic] + fn too_large_requirement_construction_fails() { + let accounts = default_accounts(); + Multisig::new(3, vec![accounts.alice, accounts.bob]); + } + + #[ink::test] + fn add_owner_works() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + let owners = contract.owners.len(); + contract.add_owner(accounts.frank); + assert_eq!(contract.owners.len(), owners + 1); + assert!(contract.is_owner.contains(accounts.frank)); + assert_eq!(test::recorded_events().count(), 1); + } + + #[ink::test] + #[should_panic] + fn add_existing_owner_fails() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + contract.add_owner(accounts.bob); + } + + #[ink::test] + #[should_panic] + fn add_owner_permission_denied() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_owner(); + contract.add_owner(accounts.frank); + } + + #[ink::test] + fn remove_owner_works() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + let owners = contract.owners.len(); + contract.remove_owner(accounts.alice); + assert_eq!(contract.owners.len(), owners - 1); + assert!(!contract.is_owner.contains(accounts.alice)); + assert_eq!(test::recorded_events().count(), 1); + } + + #[ink::test] + #[should_panic] + fn remove_owner_nonexisting_fails() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + contract.remove_owner(accounts.django); + } + + #[ink::test] + #[should_panic] + fn remove_owner_permission_denied() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_owner(); + contract.remove_owner(accounts.alice); + } + + #[ink::test] + fn replace_owner_works() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + let owners = contract.owners.len(); + contract.replace_owner(accounts.alice, accounts.django); + assert_eq!(contract.owners.len(), owners); + assert!(!contract.is_owner.contains(accounts.alice)); + assert!(contract.is_owner.contains(accounts.django)); + assert_eq!(test::recorded_events().count(), 2); + } + + #[ink::test] + #[should_panic] + fn replace_owner_existing_fails() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + contract.replace_owner(accounts.alice, accounts.bob); + } + + #[ink::test] + #[should_panic] + fn replace_owner_nonexisting_fails() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_wallet(); + contract.replace_owner(accounts.django, accounts.frank); + } + + #[ink::test] + #[should_panic] + fn replace_owner_permission_denied() { + let accounts = default_accounts(); + let mut contract = build_contract(); + set_from_owner(); + contract.replace_owner(accounts.alice, accounts.django); + } + + #[ink::test] + fn change_requirement_works() { + let mut contract = build_contract(); + assert_eq!(contract.requirement, 2); + set_from_wallet(); + contract.change_requirement(3); + assert_eq!(contract.requirement, 3); + assert_eq!(test::recorded_events().count(), 1); + } + + #[ink::test] + #[should_panic] + fn change_requirement_too_high() { + let mut contract = build_contract(); + set_from_wallet(); + contract.change_requirement(4); + } + + #[ink::test] + #[should_panic] + fn change_requirement_zero_fails() { + let mut contract = build_contract(); + set_from_wallet(); + contract.change_requirement(0); + } + + #[ink::test] + fn submit_transaction_works() { + submit_transaction(); + } + + #[ink::test] + #[should_panic] + fn submit_transaction_no_owner_fails() { + let mut contract = build_contract(); + set_from_no_owner(); + contract.submit_transaction(Transaction::change_requirement(1)); + } + + #[ink::test] + #[should_panic] + fn submit_transaction_wallet_fails() { + let mut contract = build_contract(); + set_from_wallet(); + contract.submit_transaction(Transaction::change_requirement(1)); + } + + #[ink::test] + fn cancel_transaction_works() { + let mut contract = submit_transaction(); + set_from_wallet(); + contract.cancel_transaction(0); + assert_eq!(contract.transaction_list.transactions.len(), 0); + assert_eq!(test::recorded_events().count(), 3); + } + + #[ink::test] + fn cancel_transaction_nonexisting() { + let mut contract = submit_transaction(); + set_from_wallet(); + contract.cancel_transaction(1); + assert_eq!(contract.transaction_list.transactions.len(), 1); + assert_eq!(test::recorded_events().count(), 2); + } + + #[ink::test] + #[should_panic] + fn cancel_transaction_no_permission() { + let mut contract = submit_transaction(); + contract.cancel_transaction(0); + } + + #[ink::test] + fn confirm_transaction_works() { + let mut contract = submit_transaction(); + let accounts = default_accounts(); + set_caller(accounts.bob); + contract.confirm_transaction(0); + assert_eq!(test::recorded_events().count(), 3); + contract.confirmations.get((0, accounts.bob)).unwrap(); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 2); + } + + #[ink::test] + fn revoke_confirmations() { + // given + let mut contract = submit_transaction(); + let accounts = default_accounts(); + // Confirm by Bob + set_caller(accounts.bob); + contract.confirm_transaction(0); + // Confirm by Eve + set_caller(accounts.eve); + contract.confirm_transaction(0); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 3); + // Revoke from Eve + contract.revoke_confirmation(0); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 2); + // Revoke from Bob + set_caller(accounts.bob); + contract.revoke_confirmation(0); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); + } + + #[ink::test] + fn confirm_transaction_already_confirmed() { + let mut contract = submit_transaction(); + let accounts = default_accounts(); + set_caller(accounts.alice); + contract.confirm_transaction(0); + assert_eq!(test::recorded_events().count(), 2); + contract.confirmations.get((0, accounts.alice)).unwrap(); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); + } + + #[ink::test] + #[should_panic] + fn confirm_transaction_no_owner_fail() { + let mut contract = submit_transaction(); + set_from_no_owner(); + contract.confirm_transaction(0); + } + + #[ink::test] + fn revoke_transaction_works() { + let mut contract = submit_transaction(); + let accounts = default_accounts(); + set_caller(accounts.alice); + contract.revoke_confirmation(0); + assert_eq!(test::recorded_events().count(), 3); + assert!(!contract.confirmations.contains((0, accounts.alice))); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 0); + } + + #[ink::test] + fn revoke_transaction_no_confirmer() { + let mut contract = submit_transaction(); + let accounts = default_accounts(); + set_caller(accounts.bob); + contract.revoke_confirmation(0); + assert_eq!(test::recorded_events().count(), 2); + assert!(contract.confirmations.contains((0, accounts.alice))); + assert_eq!(contract.confirmation_count.get(0).unwrap(), 1); + } + + #[ink::test] + #[should_panic] + fn revoke_transaction_no_owner_fail() { + let mut contract = submit_transaction(); + let accounts = default_accounts(); + set_caller(accounts.django); + contract.revoke_confirmation(0); + } + + #[ink::test] + fn execute_transaction_works() { + // Execution of calls is currently unsupported in off-chain test. + // Calling `execute_transaction` panics in any case. + } + } +} diff --git a/integration-tests/payment-channel/.gitignore b/integration-tests/payment-channel/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/payment-channel/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/payment-channel/Cargo.toml b/integration-tests/payment-channel/Cargo.toml new file mode 100755 index 00000000000..977befcfb25 --- /dev/null +++ b/integration-tests/payment-channel/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "payment_channel" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +hex-literal = { version = "0.4.1" } +sp-core = { version = "21.0.0", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "sp-core/std", +] + +ink-as-dependency = [] diff --git a/integration-tests/payment-channel/lib.rs b/integration-tests/payment-channel/lib.rs new file mode 100755 index 00000000000..877d039370a --- /dev/null +++ b/integration-tests/payment-channel/lib.rs @@ -0,0 +1,590 @@ +//! # Payment Channel +//! +//! This implements a payment channel between two parties. +//! +//! ## Warning +//! +//! This contract is an *example*. It is neither audited nor endorsed for production use. +//! Do **not** rely on it to keep anything of value secure. +//! +//! ## Overview +//! +//! Each instantiation of this contract creates a payment channel between a `sender` and a +//! `recipient`. It uses ECDSA signatures to ensure that the `recipient` can only claim +//! the funds if it is signed by the `sender`. +//! +//! ## Error Handling +//! +//! The only panic in the contract is when the signature is invalid. For all other +//! error cases an error is returned. Possible errors are defined in the `Error` enum. +//! +//! ## Interface +//! +//! The interface is modelled after [this blog post](https://programtheblockchain.com/posts/2018/03/02/building-long-lived-payment-channels) +//! +//! ### Deposits +//! +//! The creator of the contract, i.e the `sender`, can deposit funds to the payment +//! channel while creating the payment channel. Any subsequent deposits can be made by +//! transferring funds to the contract's address. +//! +//! ### Withdrawals +//! +//! The `recipient` can `withdraw` from the payment channel anytime by submitting the last +//! `signature` received from the `sender`. +//! +//! The `sender` can only `withdraw` by terminating the payment channel. This is +//! done by calling `start_sender_close` to set an expiration with a subsequent call +//! of `claim_timeout` to claim the funds. This will terminate the payment channel. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod payment_channel { + + /// Struct for storing the payment channel details. + /// The creator of the contract, i.e the `sender`, can deposit funds to the payment + /// channel while deploying the contract. + #[ink(storage)] + pub struct PaymentChannel { + /// The `AccountId` of the sender of the payment channel. + sender: AccountId, + /// The `AccountId` of the recipient of the payment channel. + recipient: AccountId, + /// The `Timestamp` at which the contract expires. The field is optional. + /// The contract never expires if set to `None`. + expiration: Option, + /// The `Amount` withdrawn by the recipient. + withdrawn: Balance, + /// The `Timestamp` which will be added to the current time when the sender + /// wishes to close the channel. This will be set at the time of contract + /// instantiation. + close_duration: Timestamp, + } + + /// Errors that can occur upon calling this contract. + #[derive(Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Returned if caller is not the `sender` while required to. + CallerIsNotSender, + /// Returned if caller is not the `recipient` while required to. + CallerIsNotRecipient, + /// Returned if the requested withdrawal amount is less than the amount + /// that is already already withdrawn. + AmountIsLessThanWithdrawn, + /// Returned if the requested transfer failed. This can be the case if the + /// contract does not have sufficient free funds or if the transfer would + /// have brought the contract's balance below minimum balance. + TransferFailed, + /// Returned if the contract hasn't expired yet and the `sender` wishes to + /// close the channel. + NotYetExpired, + /// Returned if the signature is invalid. + InvalidSignature, + } + + /// Type alias for the contract's `Result` type. + pub type Result = core::result::Result; + + /// Emitted when the sender starts closing the channel. + #[ink(event)] + pub struct SenderCloseStarted { + expiration: Timestamp, + close_duration: Timestamp, + } + + impl PaymentChannel { + /// The only constructor of the contract. + /// + /// The arguments `recipient` and `close_duration` are required. + /// + /// `expiration` will be set to `None`, so that the contract will + /// never expire. `sender` can call `start_sender_close` to override + /// this. `sender` will be able to claim the remaining balance by calling + /// `claim_timeout` after `expiration` has passed. + #[ink(constructor)] + pub fn new(recipient: AccountId, close_duration: Timestamp) -> Self { + Self { + sender: Self::env().caller(), + recipient, + expiration: None, + withdrawn: 0, + close_duration, + } + } + + /// The `recipient` can close the payment channel anytime. The specified + /// `amount` will be sent to the `recipient` and the remainder will go + /// back to the `sender`. + #[ink(message)] + pub fn close(&mut self, amount: Balance, signature: [u8; 65]) -> Result<()> { + self.close_inner(amount, signature)?; + self.env().terminate_contract(self.sender); + } + + /// We split this out in order to make testing `close` simpler. + fn close_inner(&mut self, amount: Balance, signature: [u8; 65]) -> Result<()> { + if self.env().caller() != self.recipient { + return Err(Error::CallerIsNotRecipient) + } + + if amount < self.withdrawn { + return Err(Error::AmountIsLessThanWithdrawn) + } + + // Signature validation + if !self.is_signature_valid(amount, signature) { + return Err(Error::InvalidSignature) + } + + // We checked that amount >= self.withdrawn + #[allow(clippy::arithmetic_side_effects)] + self.env() + .transfer(self.recipient, amount - self.withdrawn) + .map_err(|_| Error::TransferFailed)?; + + Ok(()) + } + + /// If the `sender` wishes to close the channel and withdraw the funds they can + /// do so by setting the `expiration`. If the `expiration` is reached, the + /// sender will be able to call `claim_timeout` to claim the remaining funds + /// and the channel will be terminated. This emits an event that the recipient can + /// listen to in order to withdraw the funds before the `expiration`. + #[ink(message)] + pub fn start_sender_close(&mut self) -> Result<()> { + if self.env().caller() != self.sender { + return Err(Error::CallerIsNotSender) + } + + let now = self.env().block_timestamp(); + let expiration = now.checked_add(self.close_duration).unwrap(); + + self.env().emit_event(SenderCloseStarted { + expiration, + close_duration: self.close_duration, + }); + + self.expiration = Some(expiration); + + Ok(()) + } + + /// If the timeout is reached (`current_time >= expiration`) without the + /// recipient closing the channel, then the remaining balance is released + /// back to the `sender`. + #[ink(message)] + pub fn claim_timeout(&mut self) -> Result<()> { + match self.expiration { + Some(expiration) => { + // expiration is set. Check if it's reached and if so, release the + // funds and terminate the contract. + let now = self.env().block_timestamp(); + if now < expiration { + return Err(Error::NotYetExpired) + } + + self.env().terminate_contract(self.sender); + } + + None => Err(Error::NotYetExpired), + } + } + + /// The `recipient` can withdraw the funds from the channel at any time. + #[ink(message)] + pub fn withdraw(&mut self, amount: Balance, signature: [u8; 65]) -> Result<()> { + if self.env().caller() != self.recipient { + return Err(Error::CallerIsNotRecipient) + } + + // Signature validation + if !self.is_signature_valid(amount, signature) { + return Err(Error::InvalidSignature) + } + + // Make sure there's something to withdraw (guards against underflow) + if amount < self.withdrawn { + return Err(Error::AmountIsLessThanWithdrawn) + } + + // We checked that amount >= self.withdrawn + #[allow(clippy::arithmetic_side_effects)] + let amount_to_withdraw = amount - self.withdrawn; + self.withdrawn.checked_add(amount_to_withdraw).unwrap(); + + self.env() + .transfer(self.recipient, amount_to_withdraw) + .map_err(|_| Error::TransferFailed)?; + + Ok(()) + } + + /// Returns the `sender` of the contract. + #[ink(message)] + pub fn get_sender(&self) -> AccountId { + self.sender + } + + /// Returns the `recipient` of the contract. + #[ink(message)] + pub fn get_recipient(&self) -> AccountId { + self.recipient + } + + /// Returns the `expiration` of the contract. + #[ink(message)] + pub fn get_expiration(&self) -> Option { + self.expiration + } + + /// Returns the `withdrawn` amount of the contract. + #[ink(message)] + pub fn get_withdrawn(&self) -> Balance { + self.withdrawn + } + + /// Returns the `close_duration` of the contract. + #[ink(message)] + pub fn get_close_duration(&self) -> Timestamp { + self.close_duration + } + + /// Returns the `balance` of the contract. + #[ink(message)] + pub fn get_balance(&self) -> Balance { + self.env().balance() + } + } + + #[ink(impl)] + impl PaymentChannel { + fn is_signature_valid(&self, amount: Balance, signature: [u8; 65]) -> bool { + let encodable = (self.env().account_id(), amount); + let mut message = + ::Type::default(); + ink::env::hash_encoded::( + &encodable, + &mut message, + ); + + let mut pub_key = [0; 33]; + ink::env::ecdsa_recover(&signature, &message, &mut pub_key) + .unwrap_or_else(|err| panic!("recover failed: {err:?}")); + let mut signature_account_id = [0; 32]; + ::hash( + &pub_key, + &mut signature_account_id, + ); + + self.recipient == signature_account_id.into() + } + } + + #[cfg(test)] + mod tests { + use super::*; + + use hex_literal; + use sp_core::{ + Encode, + Pair, + }; + + fn default_accounts( + ) -> ink::env::test::DefaultAccounts { + ink::env::test::default_accounts::() + } + + fn set_next_caller(caller: AccountId) { + ink::env::test::set_caller::(caller); + } + + fn set_account_balance(account: AccountId, balance: Balance) { + ink::env::test::set_account_balance::( + account, balance, + ); + } + + fn get_account_balance(account: AccountId) -> Balance { + ink::env::test::get_account_balance::(account) + .expect("Cannot get account balance") + } + + fn advance_block() { + ink::env::test::advance_block::(); + } + + fn get_current_time() -> Timestamp { + let since_the_epoch = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards"); + since_the_epoch.as_secs() + + since_the_epoch.subsec_nanos() as u64 / 1_000_000_000 + } + + fn get_dan() -> AccountId { + // Use Dan's seed + // `subkey inspect //Dan --scheme Ecdsa --output-type json | jq .secretSeed` + let seed = hex_literal::hex!( + "c31fa562972de437802e0df146b16146349590b444db41f7e3eb9deedeee6f64" + ); + let pair = sp_core::ecdsa::Pair::from_seed(&seed); + let pub_key = pair.public(); + let compressed_pub_key: [u8; 33] = pub_key.encode()[..] + .try_into() + .expect("slice with incorrect length"); + let mut account_id = [0; 32]; + ::hash( + &compressed_pub_key, + &mut account_id, + ); + account_id.into() + } + + fn contract_id() -> AccountId { + let accounts = default_accounts(); + let contract_id = accounts.charlie; + ink::env::test::set_callee::(contract_id); + contract_id + } + + fn sign(contract_id: AccountId, amount: Balance) -> [u8; 65] { + let encodable = (contract_id, amount); + let mut hash = + ::Type::default(); // 256-bit buffer + ink::env::hash_encoded::(&encodable, &mut hash); + + // Use Dan's seed + // `subkey inspect //Dan --scheme Ecdsa --output-type json | jq .secretSeed` + let seed = hex_literal::hex!( + "c31fa562972de437802e0df146b16146349590b444db41f7e3eb9deedeee6f64" + ); + let pair = sp_core::ecdsa::Pair::from_seed(&seed); + + let signature = pair.sign_prehashed(&hash); + signature.0 + } + + #[ink::test] + fn test_deposit() { + // given + let accounts = default_accounts(); + let initial_balance = 10_000; + let close_duration = 360_000; + let mock_deposit_value = 1_000; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(accounts.bob, initial_balance); + + // when + // Push the new execution context with Alice as the caller and + // the `mock_deposit_value` as the value deposited. + // Note: Currently there is no way to transfer funds to the contract. + set_next_caller(accounts.alice); + let payment_channel = PaymentChannel::new(accounts.bob, close_duration); + let contract_id = contract_id(); + set_account_balance(contract_id, mock_deposit_value); + + // then + assert_eq!(payment_channel.get_balance(), mock_deposit_value); + } + + #[ink::test] + fn test_close() { + // given + let accounts = default_accounts(); + let dan = get_dan(); + let close_duration = 360_000; + let mock_deposit_value = 1_000; + let amount = 500; + let initial_balance = 10_000; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(dan, initial_balance); + + // when + set_next_caller(accounts.alice); + let mut payment_channel = PaymentChannel::new(dan, close_duration); + let contract_id = contract_id(); + set_account_balance(contract_id, mock_deposit_value); + set_next_caller(dan); + let signature = sign(contract_id, amount); + + // then + let should_close = move || payment_channel.close(amount, signature).unwrap(); + ink::env::test::assert_contract_termination::( + should_close, + accounts.alice, + amount, + ); + assert_eq!(get_account_balance(dan), initial_balance + amount); + } + + #[ink::test] + fn close_fails_invalid_signature() { + // given + let accounts = default_accounts(); + let dan = get_dan(); + let mock_deposit_value = 1_000; + let close_duration = 360_000; + let amount = 400; + let unexpected_amount = amount + 1; + let initial_balance = 10_000; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(dan, initial_balance); + + // when + set_next_caller(accounts.alice); + let mut payment_channel = PaymentChannel::new(dan, close_duration); + let contract_id = contract_id(); + set_account_balance(contract_id, mock_deposit_value); + set_next_caller(dan); + let signature = sign(contract_id, amount); + + // then + let res = payment_channel.close_inner(unexpected_amount, signature); + assert!(res.is_err(), "Expected an error, got {res:?} instead."); + assert_eq!(res.unwrap_err(), Error::InvalidSignature,); + } + + #[ink::test] + fn test_withdraw() { + // given + let accounts = default_accounts(); + let dan = get_dan(); + let initial_balance = 10_000; + let mock_deposit_value = 1_000; + let close_duration = 360_000; + let amount = 500; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(dan, initial_balance); + + // when + set_next_caller(accounts.alice); + let mut payment_channel = PaymentChannel::new(dan, close_duration); + let contract_id = contract_id(); + set_account_balance(contract_id, mock_deposit_value); + + set_next_caller(dan); + let signature = sign(contract_id, amount); + payment_channel + .withdraw(amount, signature) + .expect("withdraw failed"); + + // then + assert_eq!(payment_channel.get_balance(), amount); + assert_eq!(get_account_balance(dan), initial_balance + amount); + } + + #[ink::test] + fn withdraw_fails_invalid_signature() { + // given + let accounts = default_accounts(); + let dan = get_dan(); + let initial_balance = 10_000; + let close_duration = 360_000; + let amount = 400; + let unexpected_amount = amount + 1; + let mock_deposit_value = 1_000; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(dan, initial_balance); + + // when + set_next_caller(accounts.alice); + let mut payment_channel = PaymentChannel::new(dan, close_duration); + let contract_id = contract_id(); + set_account_balance(contract_id, mock_deposit_value); + set_next_caller(dan); + let signature = sign(contract_id, amount); + + // then + let res = payment_channel.withdraw(unexpected_amount, signature); + assert!(res.is_err(), "Expected an error, got {res:?} instead."); + assert_eq!(res.unwrap_err(), Error::InvalidSignature,); + } + + #[ink::test] + fn test_start_sender_close() { + // given + let accounts = default_accounts(); + let initial_balance = 10_000; + let mock_deposit_value = 1_000; + let close_duration = 1; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(accounts.bob, initial_balance); + + // when + set_next_caller(accounts.alice); + let mut payment_channel = PaymentChannel::new(accounts.bob, close_duration); + let contract_id = contract_id(); + set_account_balance(contract_id, mock_deposit_value); + + payment_channel + .start_sender_close() + .expect("start_sender_close failed"); + advance_block(); + + // then + let now = get_current_time(); + assert!(now > payment_channel.get_expiration().unwrap()); + } + + #[ink::test] + fn test_claim_timeout() { + // given + let accounts = default_accounts(); + let initial_balance = 10_000; + let close_duration = 1; + let mock_deposit_value = 1_000; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(accounts.bob, initial_balance); + + // when + set_next_caller(accounts.alice); + let contract_id = contract_id(); + let mut payment_channel = PaymentChannel::new(accounts.bob, close_duration); + set_account_balance(contract_id, mock_deposit_value); + + payment_channel + .start_sender_close() + .expect("start_sender_close failed"); + advance_block(); + + // then + let should_close = move || payment_channel.claim_timeout().unwrap(); + ink::env::test::assert_contract_termination::( + should_close, + accounts.alice, + mock_deposit_value, + ); + assert_eq!( + get_account_balance(accounts.alice), + initial_balance + mock_deposit_value + ); + } + + #[ink::test] + fn test_getters() { + // given + let accounts = default_accounts(); + let initial_balance = 10_000; + let mock_deposit_value = 1_000; + let close_duration = 360_000; + set_account_balance(accounts.alice, initial_balance); + set_account_balance(accounts.bob, initial_balance); + + // when + set_next_caller(accounts.alice); + let contract_id = contract_id(); + let payment_channel = PaymentChannel::new(accounts.bob, close_duration); + set_account_balance(contract_id, mock_deposit_value); + + // then + assert_eq!(payment_channel.get_sender(), accounts.alice); + assert_eq!(payment_channel.get_recipient(), accounts.bob); + assert_eq!(payment_channel.get_balance(), mock_deposit_value); + assert_eq!(payment_channel.get_close_duration(), close_duration); + assert_eq!(payment_channel.get_withdrawn(), 0); + } + } +} diff --git a/integration-tests/psp22-extension/.gitignore b/integration-tests/psp22-extension/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/psp22-extension/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/psp22-extension/Cargo.toml b/integration-tests/psp22-extension/Cargo.toml new file mode 100755 index 00000000000..58890b6cd79 --- /dev/null +++ b/integration-tests/psp22-extension/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "psp22_extension" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/psp22-extension/README.md b/integration-tests/psp22-extension/README.md new file mode 100644 index 00000000000..040662f5639 --- /dev/null +++ b/integration-tests/psp22-extension/README.md @@ -0,0 +1,47 @@ +# PSP22 Chain Extension Example + +## What is this example about? + +It is an example implementation of the +[PSP22 Fungible Token Standard](https://github.com/w3f/PSPs/blob/master/PSPs/psp-22.md) +as a chain extension, supporting a multi-token system provided by the +[FRAME assets pallet](https://docs.substrate.io/rustdocs/latest/pallet_assets/index.html). +It effectively allows ink! contracts (L2) to interact with native assets (L1) from the +chain runtime in a standardized way. + +See [this chapter](https://paritytech.github.io/ink-docs/macros-attributes/chain-extension) +in our ink! documentation for more details about chain extensions. + +There are two parts to this example: + +* Defining and calling the extension in ink!. +* Defining the extension in Substrate. + +## Chain-side Integration + +To integrate this example into Substrate you need to do two things: + +* In your runtime, use the code in + [`psp22-extension-example.rs`](runtime/psp22-extension-example.rs) + as an implementation for the trait `ChainExtension` in Substrate. + You can just copy/paste that file as a new module, e.g. `runtime/src/chain_extension.rs`. + +* In your runtime, use the implementation as the associated type `ChainExtension` of the + trait `pallet_contracts::Config`: + ```rust + impl pallet_contracts::Config for Runtime { + … + type ChainExtension = Psp22Extension; + … + } + ``` + +## ink! Integration + +See the example contract in [`lib.rs`](lib.rs). + +## Disclaimer + +:warning: This is not a feature-complete or production-ready PSP22 implementation. This +example currently lacks proper error management, precise weight accounting, tests (these +all might be added at a later point). diff --git a/integration-tests/psp22-extension/lib.rs b/integration-tests/psp22-extension/lib.rs new file mode 100755 index 00000000000..3691ca280f4 --- /dev/null +++ b/integration-tests/psp22-extension/lib.rs @@ -0,0 +1,265 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::{ + env::Environment, + prelude::vec::Vec, +}; + +type DefaultAccountId = ::AccountId; +type DefaultBalance = ::Balance; + +#[ink::chain_extension(extension = 13)] +pub trait Psp22Extension { + type ErrorCode = Psp22Error; + + // PSP22 Metadata interfaces + + #[ink(function = 0x3d26)] + fn token_name(asset_id: u32) -> Result>; + + #[ink(function = 0x3420)] + fn token_symbol(asset_id: u32) -> Result>; + + #[ink(function = 0x7271)] + fn token_decimals(asset_id: u32) -> Result; + + // PSP22 interface queries + + #[ink(function = 0x162d)] + fn total_supply(asset_id: u32) -> Result; + + #[ink(function = 0x6568)] + fn balance_of(asset_id: u32, owner: DefaultAccountId) -> Result; + + #[ink(function = 0x4d47)] + fn allowance( + asset_id: u32, + owner: DefaultAccountId, + spender: DefaultAccountId, + ) -> Result; + + // PSP22 transfer + #[ink(function = 0xdb20)] + fn transfer(asset_id: u32, to: DefaultAccountId, value: DefaultBalance) + -> Result<()>; + + // PSP22 transfer_from + #[ink(function = 0x54b3)] + fn transfer_from( + asset_id: u32, + from: DefaultAccountId, + to: DefaultAccountId, + value: DefaultBalance, + ) -> Result<()>; + + // PSP22 approve + #[ink(function = 0xb20f)] + fn approve( + asset_id: u32, + spender: DefaultAccountId, + value: DefaultBalance, + ) -> Result<()>; + + // PSP22 increase_allowance + #[ink(function = 0x96d6)] + fn increase_allowance( + asset_id: u32, + spender: DefaultAccountId, + value: DefaultBalance, + ) -> Result<()>; + + // PSP22 decrease_allowance + #[ink(function = 0xfecb)] + fn decrease_allowance( + asset_id: u32, + spender: DefaultAccountId, + value: DefaultBalance, + ) -> Result<()>; +} + +#[derive(Debug, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum Psp22Error { + TotalSupplyFailed, +} + +pub type Result = core::result::Result; + +impl From for Psp22Error { + fn from(_: ink::scale::Error) -> Self { + panic!("encountered unexpected invalid SCALE encoding") + } +} + +impl ink::env::chain_extension::FromStatusCode for Psp22Error { + fn from_status_code(status_code: u32) -> core::result::Result<(), Self> { + match status_code { + 0 => Ok(()), + 1 => Err(Self::TotalSupplyFailed), + _ => panic!("encountered unknown status code"), + } + } +} + +/// An environment using default ink environment types, with PSP-22 extension included +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(TypeInfo)] +pub enum CustomEnvironment {} + +impl Environment for CustomEnvironment { + const MAX_EVENT_TOPICS: usize = + ::MAX_EVENT_TOPICS; + + type AccountId = DefaultAccountId; + type Balance = DefaultBalance; + type Hash = ::Hash; + type Timestamp = ::Timestamp; + type BlockNumber = ::BlockNumber; + + type ChainExtension = crate::Psp22Extension; +} + +#[ink::contract(env = crate::CustomEnvironment)] +mod psp22_ext { + use super::{ + Result, + Vec, + }; + + /// A chain extension which implements the PSP-22 fungible token standard. + /// For more details see + #[ink(storage)] + #[derive(Default)] + pub struct Psp22Extension {} + + impl Psp22Extension { + /// Creates a new instance of this contract. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + // PSP22 Metadata interfaces + + /// Returns the token name of the specified asset. + #[ink(message, selector = 0x3d261bd4)] + pub fn token_name(&self, asset_id: u32) -> Result> { + self.env().extension().token_name(asset_id) + } + + /// Returns the token symbol of the specified asset. + #[ink(message, selector = 0x34205be5)] + pub fn token_symbol(&self, asset_id: u32) -> Result> { + self.env().extension().token_symbol(asset_id) + } + + /// Returns the token decimals of the specified asset. + #[ink(message, selector = 0x7271b782)] + pub fn token_decimals(&self, asset_id: u32) -> Result { + self.env().extension().token_decimals(asset_id) + } + + // PSP22 interface queries + + /// Returns the total token supply of the specified asset. + #[ink(message, selector = 0x162df8c2)] + pub fn total_supply(&self, asset_id: u32) -> Result { + self.env().extension().total_supply(asset_id) + } + + /// Returns the account balance for the specified asset & owner. + #[ink(message, selector = 0x6568382f)] + pub fn balance_of(&self, asset_id: u32, owner: AccountId) -> Result { + self.env().extension().balance_of(asset_id, owner) + } + + /// Returns the amount which `spender` is still allowed to withdraw from `owner` + /// for the specified asset. + #[ink(message, selector = 0x4d47d921)] + pub fn allowance( + &self, + asset_id: u32, + owner: AccountId, + spender: AccountId, + ) -> Result { + self.env().extension().allowance(asset_id, owner, spender) + } + + // PSP22 transfer + + /// Transfers `value` amount of specified asset from the caller's account to the + /// account `to`. + #[ink(message, selector = 0xdb20f9f5)] + pub fn transfer( + &mut self, + asset_id: u32, + to: AccountId, + value: Balance, + ) -> Result<()> { + self.env().extension().transfer(asset_id, to, value) + } + + // PSP22 transfer_from + + /// Transfers `value` amount of specified asset on the behalf of `from` to the + /// account `to`. + #[ink(message, selector = 0x54b3c76e)] + pub fn transfer_from( + &mut self, + asset_id: u32, + from: AccountId, + to: AccountId, + value: Balance, + ) -> Result<()> { + self.env() + .extension() + .transfer_from(asset_id, from, to, value) + } + + // PSP22 approve + + /// Allows `spender` to withdraw from the caller's account multiple times, up to + /// the `value` amount of the specified asset. + #[ink(message, selector = 0xb20f1bbd)] + pub fn approve( + &mut self, + asset_id: u32, + spender: AccountId, + value: Balance, + ) -> Result<()> { + self.env().extension().approve(asset_id, spender, value) + } + + // PSP22 increase_allowance + + /// Atomically increases the allowance for the specified asset granted to + /// `spender` by the caller. + #[ink(message, selector = 0x96d6b57a)] + pub fn increase_allowance( + &mut self, + asset_id: u32, + spender: AccountId, + value: Balance, + ) -> Result<()> { + self.env() + .extension() + .increase_allowance(asset_id, spender, value) + } + + // PSP22 decrease_allowance + + /// Atomically decreases the allowance for the specified asset granted to + /// `spender` by the caller. + #[ink(message, selector = 0xfecb57d5)] + pub fn decrease_allowance( + &mut self, + asset_id: u32, + spender: AccountId, + value: Balance, + ) -> Result<()> { + self.env() + .extension() + .decrease_allowance(asset_id, spender, value) + } + } +} diff --git a/integration-tests/psp22-extension/runtime/psp22-extension-example.rs b/integration-tests/psp22-extension/runtime/psp22-extension-example.rs new file mode 100644 index 00000000000..aa11d819cdb --- /dev/null +++ b/integration-tests/psp22-extension/runtime/psp22-extension-example.rs @@ -0,0 +1,446 @@ +use codec::{ + Decode, + Encode, + MaxEncodedLen, +}; +use frame_support::{ + dispatch::RawOrigin, + log::{ + error, + trace, + }, + pallet_prelude::*, + traits::fungibles::{ + approvals::{ + Inspect as AllowanceInspect, + Mutate as AllowanceMutate, + }, + Inspect, + InspectMetadata, + Transfer, + }, +}; +use pallet_assets::{ + self, + WeightInfo, +}; +use pallet_contracts::chain_extension::{ + ChainExtension, + Environment, + Ext, + InitState, + RetVal, + SysConfig, + UncheckedFrom, +}; +use sp_runtime::{ + traits::{ + Saturating, + StaticLookup, + Zero, + }, + DispatchError, +}; + +#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] +struct Psp22BalanceOfInput { + asset_id: AssetId, + owner: AccountId, +} + +#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] +struct Psp22AllowanceInput { + asset_id: AssetId, + owner: AccountId, + spender: AccountId, +} + +#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] +struct Psp22TransferInput { + asset_id: AssetId, + to: AccountId, + value: Balance, +} + +#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] +struct Psp22TransferFromInput { + asset_id: AssetId, + from: AccountId, + to: AccountId, + value: Balance, +} + +#[derive(Debug, PartialEq, Encode, Decode, MaxEncodedLen)] +struct Psp22ApproveInput { + asset_id: AssetId, + spender: AccountId, + value: Balance, +} + +#[derive(Default)] +pub struct Psp22Extension; + +fn convert_err(err_msg: &'static str) -> impl FnOnce(DispatchError) -> DispatchError { + move |err| { + trace!( + target: "runtime", + "PSP22 Transfer failed:{:?}", + err + ); + DispatchError::Other(err_msg) + } +} + +/// We're using enums for function IDs because contrary to raw u16 it enables +/// exhaustive matching, which results in cleaner code. +enum FuncId { + Metadata(Metadata), + Query(Query), + Transfer, + TransferFrom, + Approve, + IncreaseAllowance, + DecreaseAllowance, +} + +#[derive(Debug)] +enum Metadata { + Name, + Symbol, + Decimals, +} + +#[derive(Debug)] +enum Query { + TotalSupply, + BalanceOf, + Allowance, +} + +impl TryFrom for FuncId { + type Error = DispatchError; + + fn try_from(func_id: u16) -> Result { + let id = match func_id { + // Note: We use the first two bytes of PSP22 interface selectors as function + // IDs, While we can use anything here, it makes sense from a + // convention perspective. + 0x3d26 => Self::Metadata(Metadata::Name), + 0x3420 => Self::Metadata(Metadata::Symbol), + 0x7271 => Self::Metadata(Metadata::Decimals), + 0x162d => Self::Query(Query::TotalSupply), + 0x6568 => Self::Query(Query::BalanceOf), + 0x4d47 => Self::Query(Query::Allowance), + 0xdb20 => Self::Transfer, + 0x54b3 => Self::TransferFrom, + 0xb20f => Self::Approve, + 0x96d6 => Self::IncreaseAllowance, + 0xfecb => Self::DecreaseAllowance, + _ => { + error!("Called an unregistered `func_id`: {:}", func_id); + return Err(DispatchError::Other("Unimplemented func_id")) + } + }; + + Ok(id) + } +} + +fn metadata( + func_id: Metadata, + env: Environment, +) -> Result<(), DispatchError> +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + E: Ext, +{ + let mut env = env.buf_in_buf_out(); + let asset_id = env.read_as()?; + let result = match func_id { + Metadata::Name => { + as InspectMetadata>::name(&asset_id) + .encode() + } + Metadata::Symbol => { + as InspectMetadata>::symbol(&asset_id) + .encode() + } + Metadata::Decimals => { + as InspectMetadata>::decimals( + &asset_id, + ) + .encode() + } + }; + trace!( + target: "runtime", + "[ChainExtension] PSP22Metadata::{:?}", + func_id + ); + env.write(&result, false, None) + .map_err(convert_err("ChainExtension failed to call PSP22Metadata")) +} + +fn query( + func_id: Query, + env: Environment, +) -> Result<(), DispatchError> +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + E: Ext, +{ + let mut env = env.buf_in_buf_out(); + let result = match func_id { + Query::TotalSupply => { + let asset_id = env.read_as()?; + as Inspect>::total_issuance(asset_id) + } + Query::BalanceOf => { + let input: Psp22BalanceOfInput = env.read_as()?; + as Inspect>::balance( + input.asset_id, + &input.owner, + ) + } + Query::Allowance => { + let input: Psp22AllowanceInput = env.read_as()?; + as AllowanceInspect>::allowance( + input.asset_id, + &input.owner, + &input.spender, + ) + } + } + .encode(); + trace!( + target: "runtime", + "[ChainExtension] PSP22::{:?}", + func_id + ); + env.write(&result, false, None) + .map_err(convert_err("ChainExtension failed to call PSP22 query")) +} + +fn transfer(env: Environment) -> Result<(), DispatchError> +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + E: Ext, +{ + let mut env = env.buf_in_buf_out(); + let base_weight = ::WeightInfo::transfer(); + // debug_message weight is a good approximation of the additional overhead of going + // from contract layer to substrate layer. + let overhead = Weight::from_ref_time( + ::Schedule::get() + .host_fn_weights + .debug_message, + ); + let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; + trace!( + target: "runtime", + "[ChainExtension]|call|transfer / charge_weight:{:?}", + charged_weight + ); + + let input: Psp22TransferInput = + env.read_as()?; + let sender = env.ext().caller(); + + as Transfer>::transfer( + input.asset_id, + sender, + &input.to, + input.value, + true, + ) + .map_err(convert_err("ChainExtension failed to call transfer"))?; + trace!( + target: "runtime", + "[ChainExtension]|call|transfer" + ); + + Ok(()) +} + +fn transfer_from(env: Environment) -> Result<(), DispatchError> +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + E: Ext, +{ + let mut env = env.buf_in_buf_out(); + let base_weight = ::WeightInfo::transfer(); + // debug_message weight is a good approximation of the additional overhead of going + // from contract layer to substrate layer. + let overhead = Weight::from_ref_time( + ::Schedule::get() + .host_fn_weights + .debug_message, + ); + let charged_amount = env.charge_weight(base_weight.saturating_add(overhead))?; + trace!( + target: "runtime", + "[ChainExtension]|call|transfer / charge_weight:{:?}", + charged_amount + ); + + let input: Psp22TransferFromInput = + env.read_as()?; + let spender = env.ext().caller(); + + let result = + as AllowanceMutate>::transfer_from( + input.asset_id, + &input.from, + spender, + &input.to, + input.value, + ); + trace!( + target: "runtime", + "[ChainExtension]|call|transfer_from" + ); + result.map_err(convert_err("ChainExtension failed to call transfer_from")) +} + +fn approve(env: Environment) -> Result<(), DispatchError> +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + E: Ext, +{ + let mut env = env.buf_in_buf_out(); + let base_weight = ::WeightInfo::approve_transfer(); + // debug_message weight is a good approximation of the additional overhead of going + // from contract layer to substrate layer. + let overhead = Weight::from_ref_time( + ::Schedule::get() + .host_fn_weights + .debug_message, + ); + let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; + trace!( + target: "runtime", + "[ChainExtension]|call|approve / charge_weight:{:?}", + charged_weight + ); + + let input: Psp22ApproveInput = env.read_as()?; + let owner = env.ext().caller(); + + let result = as AllowanceMutate>::approve( + input.asset_id, + owner, + &input.spender, + input.value, + ); + trace!( + target: "runtime", + "[ChainExtension]|call|approve" + ); + result.map_err(convert_err("ChainExtension failed to call approve")) +} + +fn decrease_allowance(env: Environment) -> Result<(), DispatchError> +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + E: Ext, +{ + let mut env = env.buf_in_buf_out(); + let input: Psp22ApproveInput = env.read_as()?; + if input.value.is_zero() { + return Ok(()) + } + + let base_weight = ::WeightInfo::cancel_approval() + .saturating_add(::WeightInfo::approve_transfer()); + // debug_message weight is a good approximation of the additional overhead of going + // from contract layer to substrate layer. + let overhead = Weight::from_ref_time( + ::Schedule::get() + .host_fn_weights + .debug_message, + ); + let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; + trace!( + target: "runtime", + "[ChainExtension]|call|decrease_allowance / charge_weight:{:?}", + charged_weight + ); + + let owner = env.ext().caller(); + let mut allowance = + as AllowanceInspect>::allowance( + input.asset_id, + owner, + &input.spender, + ); + >::cancel_approval( + RawOrigin::Signed(owner.clone()).into(), + input.asset_id, + T::Lookup::unlookup(input.spender.clone()), + ) + .map_err(convert_err( + "ChainExtension failed to call decrease_allowance", + ))?; + allowance.saturating_reduce(input.value); + if allowance.is_zero() { + // If reduce value was less or equal than existing allowance, it should stay none. + env.adjust_weight( + charged_weight, + ::WeightInfo::cancel_approval() + .saturating_add(overhead), + ); + return Ok(()) + } + as AllowanceMutate>::approve( + input.asset_id, + owner, + &input.spender, + allowance, + ) + .map_err(convert_err( + "ChainExtension failed to call decrease_allowance", + ))?; + + trace!( + target: "runtime", + "[ChainExtension]|call|decrease_allowance" + ); + + Ok(()) +} + +impl ChainExtension for Psp22Extension +where + T: pallet_assets::Config + pallet_contracts::Config, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, +{ + fn call( + &mut self, + env: Environment, + ) -> Result + where + E: Ext, + ::AccountId: + UncheckedFrom<::Hash> + AsRef<[u8]>, + { + let func_id = FuncId::try_from(env.func_id())?; + match func_id { + FuncId::Metadata(func_id) => metadata::(func_id, env)?, + FuncId::Query(func_id) => query::(func_id, env)?, + FuncId::Transfer => transfer::(env)?, + FuncId::TransferFrom => transfer_from::(env)?, + // This is a bit of a shortcut. It was made because the documentation + // for Mutate::approve does not specify the result of subsequent calls. + FuncId::Approve | FuncId::IncreaseAllowance => approve::(env)?, + FuncId::DecreaseAllowance => decrease_allowance(env)?, + } + + Ok(RetVal::Converging(0)) + } +} diff --git a/integration-tests/rand-extension/.gitignore b/integration-tests/rand-extension/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/rand-extension/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/rand-extension/Cargo.toml b/integration-tests/rand-extension/Cargo.toml new file mode 100755 index 00000000000..7b2f447dc5e --- /dev/null +++ b/integration-tests/rand-extension/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rand_extension" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/rand-extension/README.md b/integration-tests/rand-extension/README.md new file mode 100644 index 00000000000..19d4cc48c7a --- /dev/null +++ b/integration-tests/rand-extension/README.md @@ -0,0 +1,35 @@ +# Chain Extension Example + +## What is this example about? + +It demonstrates how to call a custom Substrate function from ink!. + +See [this chapter](https://use.ink/macros-attributes/chain-extension) +in our ink! documentation for more details. + +There are two parts to this example: + +* Defining and calling the extension in ink!. +* Defining the extension in Substrate. + +## Chain-side Integration + +To integrate this example into Substrate you need to do two things: + +* Use the code in [`chain-extension-example.rs`](runtime/chain-extension-example.rs) + as an implementation for the trait `ChainExtension` in Substrate. + You can just copy/paste the content of that file into e.g. your `runtime/src/lib.rs`. + +* Use the implementation as the associated type `ChainExtension` of the trait + `pallet_contracts::Config`: + ```rust + impl pallet_contracts::Config for Runtime { + … + type ChainExtension = FetchRandomExtension; + … + } + ``` + +## ink! Integration + +See the example contract in [`lib.rs`](lib.rs). diff --git a/integration-tests/rand-extension/lib.rs b/integration-tests/rand-extension/lib.rs new file mode 100755 index 00000000000..4fad0867c39 --- /dev/null +++ b/integration-tests/rand-extension/lib.rs @@ -0,0 +1,160 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::env::Environment; + +/// This is an example of how an ink! contract may call the Substrate +/// runtime function `RandomnessCollectiveFlip::random_seed`. See the +/// file `runtime/chain-extension-example.rs` for that implementation. +/// +/// Here we define the operations to interact with the Substrate runtime. +#[ink::chain_extension(extension = 666)] +pub trait FetchRandom { + type ErrorCode = RandomReadErr; + + /// Note: this gives the operation a corresponding `func_id` (1101 in this case), + /// and the chain-side chain extension will get the `func_id` to do further + /// operations. + #[ink(function = 1101)] + fn fetch_random(subject: [u8; 32]) -> [u8; 32]; +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum RandomReadErr { + FailGetRandomSource, +} + +impl ink::env::chain_extension::FromStatusCode for RandomReadErr { + fn from_status_code(status_code: u32) -> Result<(), Self> { + match status_code { + 0 => Ok(()), + 1 => Err(Self::FailGetRandomSource), + _ => panic!("encountered unknown status code"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(TypeInfo)] +pub enum CustomEnvironment {} + +impl Environment for CustomEnvironment { + const MAX_EVENT_TOPICS: usize = + ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = FetchRandom; +} + +#[ink::contract(env = crate::CustomEnvironment)] +mod rand_extension { + use super::RandomReadErr; + + /// Defines the storage of our contract. + /// + /// Here we store the random seed fetched from the chain. + #[ink(storage)] + pub struct RandExtension { + /// Stores a single `bool` value on the storage. + value: [u8; 32], + } + + #[ink(event)] + pub struct RandomUpdated { + #[ink(topic)] + new: [u8; 32], + } + + impl RandExtension { + /// Constructor that initializes the `bool` value to the given `init_value`. + #[ink(constructor)] + pub fn new(init_value: [u8; 32]) -> Self { + Self { value: init_value } + } + + /// Constructor that initializes the `bool` value to `false`. + /// + /// Constructors may delegate to other constructors. + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + /// Seed a random value by passing some known argument `subject` to the runtime's + /// random source. Then, update the current `value` stored in this contract with + /// the new random value. + #[ink(message)] + pub fn update(&mut self, subject: [u8; 32]) -> Result<(), RandomReadErr> { + // Get the on-chain random seed + let new_random = self.env().extension().fetch_random(subject)?; + self.value = new_random; + // Emit the `RandomUpdated` event when the random seed + // is successfully fetched. + self.env().emit_event(RandomUpdated { new: new_random }); + Ok(()) + } + + /// Simply returns the current value. + #[ink(message)] + pub fn get(&self) -> [u8; 32] { + self.value + } + } + + /// Unit tests in Rust are normally defined within such a `#[cfg(test)]` + #[cfg(test)] + mod tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + + /// We test if the default constructor does its job. + #[ink::test] + fn default_works() { + let rand_extension = RandExtension::new_default(); + assert_eq!(rand_extension.get(), [0; 32]); + } + + #[ink::test] + fn chain_extension_works() { + // given + struct MockedRandExtension; + impl ink::env::test::ChainExtension for MockedRandExtension { + /// The static function id of the chain extension. + fn ext_id(&self) -> u32 { + 666 + } + + /// The chain extension is called with the given input. + /// + /// Returns an error code and may fill the `output` buffer with a + /// SCALE encoded result. The error code is taken from the + /// `ink::env::chain_extension::FromStatusCode` implementation for + /// `RandomReadErr`. + fn call( + &mut self, + _func_id: u16, + _input: &[u8], + output: &mut Vec, + ) -> u32 { + let ret: [u8; 32] = [1; 32]; + ink::scale::Encode::encode_to(&ret, output); + 0 + } + } + ink::env::test::register_chain_extension(MockedRandExtension); + let mut rand_extension = RandExtension::new_default(); + assert_eq!(rand_extension.get(), [0; 32]); + + // when + rand_extension.update([0_u8; 32]).expect("update must work"); + + // then + assert_eq!(rand_extension.get(), [1; 32]); + } + } +} diff --git a/integration-tests/rand-extension/runtime/chain-extension-example.rs b/integration-tests/rand-extension/runtime/chain-extension-example.rs new file mode 100644 index 00000000000..4d4a4a9bcbe --- /dev/null +++ b/integration-tests/rand-extension/runtime/chain-extension-example.rs @@ -0,0 +1,58 @@ +use codec::Encode; +use frame_support::log::{ + error, + trace, +}; +use pallet_contracts::chain_extension::{ + ChainExtension, + Environment, + Ext, + InitState, + RetVal, + SysConfig, +}; +use sp_core::crypto::UncheckedFrom; +use sp_runtime::DispatchError; + +/// Contract extension for `FetchRandom` +#[derive(Default)] +pub struct FetchRandomExtension; + +impl ChainExtension for FetchRandomExtension { + fn call( + &mut self, + env: Environment, + ) -> Result + where + ::AccountId: + UncheckedFrom<::Hash> + AsRef<[u8]>, + { + let func_id = env.func_id(); + match func_id { + 1101 => { + let mut env = env.buf_in_buf_out(); + let arg: [u8; 32] = env.read_as()?; + let random_seed = crate::RandomnessCollectiveFlip::random(&arg).0; + let random_slice = random_seed.encode(); + trace!( + target: "runtime", + "[ChainExtension]|call|func_id:{:}", + func_id + ); + env.write(&random_slice, false, None).map_err(|_| { + DispatchError::Other("ChainExtension failed to call random") + })?; + } + + _ => { + error!("Called an unregistered `func_id`: {:}", func_id); + return Err(DispatchError::Other("Unimplemented func_id")) + } + } + Ok(RetVal::Converging(0)) + } + + fn enabled() -> bool { + true + } +} diff --git a/integration-tests/trait-dyn-cross-contract-calls/Cargo.toml b/integration-tests/trait-dyn-cross-contract-calls/Cargo.toml new file mode 100644 index 00000000000..45dd72e9712 --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "trait-incrementer-caller" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +dyn-traits = { path = "./traits", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } +trait-incrementer = { path = "./contracts/incrementer", default-features = false, features = ["ink-as-dependency"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "dyn-traits/std", +] +e2e-tests = [] +ink-as-dependency = [] + +# Required to be able to run e2e test with sub-contracts +[workspace] +members = [ + "contracts/incrementer", +] diff --git a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml new file mode 100644 index 00000000000..b4d45355e76 --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "trait-incrementer" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } + +dyn-traits = { path = "../../traits", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "dyn-traits/std", +] +ink-as-dependency = [] diff --git a/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs new file mode 100644 index 00000000000..7ef3912b9af --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/contracts/incrementer/lib.rs @@ -0,0 +1,56 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +pub mod incrementer { + use dyn_traits::Increment; + + /// A concrete incrementer smart contract. + #[ink(storage)] + pub struct Incrementer { + value: u64, + } + + impl Incrementer { + /// Creates a new incrementer smart contract initialized with zero. + #[ink(constructor)] + pub fn new() -> Self { + Self { + value: u64::default(), + } + } + + /// Increases the value of the incrementer by an amount. + #[ink(message)] + pub fn inc_by(&mut self, delta: u64) { + self.value = self.value.checked_add(delta).unwrap(); + } + } + + impl Increment for Incrementer { + #[ink(message)] + fn inc(&mut self) { + self.inc_by(1) + } + + #[ink(message)] + fn get(&self) -> u64 { + self.value + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn it_works() { + let mut incrementer = Incrementer::new(); + // Can call using universal call syntax using the trait. + assert_eq!(::get(&incrementer), 0); + ::inc(&mut incrementer); + // Normal call syntax possible to as long as the trait is in scope. + assert_eq!(incrementer.get(), 1); + } + } +} diff --git a/integration-tests/trait-dyn-cross-contract-calls/lib.rs b/integration-tests/trait-dyn-cross-contract-calls/lib.rs new file mode 100644 index 00000000000..2f0adad9498 --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/lib.rs @@ -0,0 +1,135 @@ +//! This crate contains the `Caller` contract with no functionality except forwarding +//! all calls to the `trait_incrementer::Incrementer` contract. +//! +//! The `Caller` doesn't use the `trait_incrementer::IncrementerRef`. Instead, +//! all interactions with the `Incrementer` is done through the wrapper from +//! `ink::contract_ref!` and the trait `dyn_traits::Increment`. +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +pub mod caller { + use dyn_traits::Increment; + + /// The caller of the incrementer smart contract. + #[ink(storage)] + pub struct Caller { + /// Here we accept a type which implements the `Incrementer` ink! trait. + incrementer: ink::contract_ref!(Increment), + } + + impl Caller { + /// Creates a new caller smart contract around the `incrementer` account id. + #[ink(constructor)] + pub fn new(incrementer: AccountId) -> Self { + Self { + incrementer: incrementer.into(), + } + } + } + + impl Increment for Caller { + #[ink(message)] + fn inc(&mut self) { + self.incrementer.inc() + } + + #[ink(message)] + fn get(&self) -> u64 { + self.incrementer.get() + } + } +} + +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests { + use super::caller::{ + Caller, + CallerRef, + }; + use dyn_traits::Increment; + use ink_e2e::ContractsBackend; + use trait_incrementer::incrementer::{ + Incrementer, + IncrementerRef, + }; + + type E2EResult = Result>; + + /// A test deploys and instantiates the `trait_incrementer::Incrementer` and + /// `trait_incrementer_caller::Caller` contracts, where the `Caller` uses the account + /// id of the `Incrementer` for instantiation. + /// + /// The test verifies that we can increment the value of the `Incrementer` contract + /// through the `Caller` contract. + #[ink_e2e::test(additional_contracts = "contracts/incrementer/Cargo.toml")] + async fn e2e_cross_contract_calls( + mut client: Client, + ) -> E2EResult<()> { + let _ = client + .upload("trait-incrementer", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `trait-incrementer` failed") + .code_hash; + + let _ = client + .upload("trait-incrementer-caller", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `trait-incrementer-caller` failed") + .code_hash; + + let mut constructor = IncrementerRef::new(); + + let incrementer = client + .instantiate("trait-incrementer", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let incrementer_call = incrementer.call::(); + + let mut constructor = CallerRef::new(incrementer.account_id.clone()); + + let caller = client + .instantiate( + "trait-incrementer-caller", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut caller_call = caller.call::(); + + // Check through the caller that the value of the incrementer is zero + let get = caller_call.get(); + let value = client + .call(&ink_e2e::alice(), &get) + .dry_run() + .await? + .return_value(); + assert_eq!(value, 0); + + // Increment the value of the incrementer via the caller + let inc = caller_call.inc(); + let _ = client + .call(&ink_e2e::alice(), &inc) + .submit() + .await + .expect("calling `inc` failed"); + + // Ask the `trait-increment` about a value. It should be updated by the caller. + // Also use `contract_ref!(Increment)` instead of `IncrementerRef` + // to check that it also works with e2e testing. + let get = incrementer_call.get(); + let value = client + .call(&ink_e2e::alice(), &get) + .dry_run() + .await? + .return_value(); + assert_eq!(value, 1); + + Ok(()) + } +} diff --git a/integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore b/integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/traits/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml b/integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml new file mode 100644 index 00000000000..46e93afbef9 --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/traits/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dyn-traits" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs b/integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs new file mode 100644 index 00000000000..bf3c0aa0f1d --- /dev/null +++ b/integration-tests/trait-dyn-cross-contract-calls/traits/lib.rs @@ -0,0 +1,16 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +//! The trait is extracted into a separate crate to show how to do cross-contract +//! calls only with traits without importing the contract. + +/// Allows to increment and get the current value. +#[ink::trait_definition] +pub trait Increment { + /// Increments the current value of the implementer by one (1). + #[ink(message)] + fn inc(&mut self); + + /// Returns the current value of the implementer. + #[ink(message)] + fn get(&self) -> u64; +} diff --git a/integration-tests/trait-erc20/.gitignore b/integration-tests/trait-erc20/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/trait-erc20/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-erc20/Cargo.toml b/integration-tests/trait-erc20/Cargo.toml new file mode 100644 index 00000000000..3582068e689 --- /dev/null +++ b/integration-tests/trait-erc20/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "trait_erc20" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/trait-erc20/lib.rs b/integration-tests/trait-erc20/lib.rs new file mode 100644 index 00000000000..56c9c739751 --- /dev/null +++ b/integration-tests/trait-erc20/lib.rs @@ -0,0 +1,549 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod erc20 { + use ink::storage::Mapping; + + /// The ERC-20 error types. + #[derive(Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Returned if not enough balance to fulfill a request is available. + InsufficientBalance, + /// Returned if not enough allowance to fulfill a request is available. + InsufficientAllowance, + } + + /// The ERC-20 result type. + pub type Result = core::result::Result; + + /// Trait implemented by all ERC-20 respecting smart contracts. + #[ink::trait_definition] + pub trait BaseErc20 { + /// Returns the total token supply. + #[ink(message)] + fn total_supply(&self) -> Balance; + + /// Returns the account balance for the specified `owner`. + #[ink(message)] + fn balance_of(&self, owner: AccountId) -> Balance; + + /// Returns the amount which `spender` is still allowed to withdraw from `owner`. + #[ink(message)] + fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance; + + /// Transfers `value` amount of tokens from the caller's account to account `to`. + #[ink(message)] + fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()>; + + /// Allows `spender` to withdraw from the caller's account multiple times, up to + /// the `value` amount. + #[ink(message)] + fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()>; + + /// Transfers `value` tokens on the behalf of `from` to the account `to`. + #[ink(message)] + fn transfer_from( + &mut self, + from: AccountId, + to: AccountId, + value: Balance, + ) -> Result<()>; + } + + /// A simple ERC-20 contract. + #[ink(storage)] + #[derive(Default)] + pub struct Erc20 { + /// Total token supply. + total_supply: Balance, + /// Mapping from owner to number of owned token. + balances: Mapping, + /// Mapping of the token amount which an account is allowed to withdraw + /// from another account. + allowances: Mapping<(AccountId, AccountId), Balance>, + } + + /// Event emitted when a token transfer occurs. + #[ink(event)] + pub struct Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + #[ink(topic)] + value: Balance, + } + + /// Event emitted when an approval occurs that `spender` is allowed to withdraw + /// up to the amount of `value` tokens from `owner`. + #[ink(event)] + pub struct Approval { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + spender: AccountId, + #[ink(topic)] + value: Balance, + } + + impl Erc20 { + /// Creates a new ERC-20 contract with the specified initial supply. + #[ink(constructor)] + pub fn new(total_supply: Balance) -> Self { + let mut balances = Mapping::default(); + let caller = Self::env().caller(); + balances.insert(caller, &total_supply); + Self::env().emit_event(Transfer { + from: None, + to: Some(caller), + value: total_supply, + }); + Self { + total_supply, + balances, + allowances: Default::default(), + } + } + } + + impl BaseErc20 for Erc20 { + /// Returns the total token supply. + #[ink(message)] + fn total_supply(&self) -> Balance { + self.total_supply + } + + /// Returns the account balance for the specified `owner`. + /// + /// Returns `0` if the account is non-existent. + #[ink(message)] + fn balance_of(&self, owner: AccountId) -> Balance { + self.balance_of_impl(&owner) + } + + /// Returns the amount which `spender` is still allowed to withdraw from `owner`. + /// + /// Returns `0` if no allowance has been set. + #[ink(message)] + fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { + self.allowance_impl(&owner, &spender) + } + + /// Transfers `value` amount of tokens from the caller's account to account `to`. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `InsufficientBalance` error if there are not enough tokens on + /// the caller's account balance. + #[ink(message)] + fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> { + let from = self.env().caller(); + self.transfer_from_to(&from, &to, value) + } + + /// Allows `spender` to withdraw from the caller's account multiple times, up to + /// the `value` amount. + /// + /// If this function is called again it overwrites the current allowance with + /// `value`. + /// + /// An `Approval` event is emitted. + #[ink(message)] + fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { + let owner = self.env().caller(); + self.allowances.insert((&owner, &spender), &value); + self.env().emit_event(Approval { + owner, + spender, + value, + }); + Ok(()) + } + + /// Transfers `value` tokens on the behalf of `from` to the account `to`. + /// + /// This can be used to allow a contract to transfer tokens on ones behalf and/or + /// to charge fees in sub-currencies, for example. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `InsufficientAllowance` error if there are not enough tokens allowed + /// for the caller to withdraw from `from`. + /// + /// Returns `InsufficientBalance` error if there are not enough tokens on + /// the account balance of `from`. + #[ink(message)] + fn transfer_from( + &mut self, + from: AccountId, + to: AccountId, + value: Balance, + ) -> Result<()> { + let caller = self.env().caller(); + let allowance = self.allowance_impl(&from, &caller); + if allowance < value { + return Err(Error::InsufficientAllowance) + } + self.transfer_from_to(&from, &to, value)?; + // We checked that allowance >= value + #[allow(clippy::arithmetic_side_effects)] + self.allowances + .insert((&from, &caller), &(allowance - value)); + Ok(()) + } + } + + #[ink(impl)] + impl Erc20 { + /// Returns the account balance for the specified `owner`. + /// + /// Returns `0` if the account is non-existent. + /// + /// # Note + /// + /// Prefer to call this method over `balance_of` since this + /// works using references which are more efficient in Wasm. + #[inline] + fn balance_of_impl(&self, owner: &AccountId) -> Balance { + self.balances.get(owner).unwrap_or_default() + } + + /// Returns the amount which `spender` is still allowed to withdraw from `owner`. + /// + /// Returns `0` if no allowance has been set. + /// + /// # Note + /// + /// Prefer to call this method over `allowance` since this + /// works using references which are more efficient in Wasm. + #[inline] + fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance { + self.allowances.get((owner, spender)).unwrap_or_default() + } + + /// Transfers `value` amount of tokens from the caller's account to account `to`. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `InsufficientBalance` error if there are not enough tokens on + /// the caller's account balance. + fn transfer_from_to( + &mut self, + from: &AccountId, + to: &AccountId, + value: Balance, + ) -> Result<()> { + let from_balance = self.balance_of_impl(from); + if from_balance < value { + return Err(Error::InsufficientBalance) + } + // We checked that from_balance >= value + #[allow(clippy::arithmetic_side_effects)] + self.balances.insert(from, &(from_balance - value)); + let to_balance = self.balance_of_impl(to); + self.balances + .insert(to, &(to_balance.checked_add(value).unwrap())); + self.env().emit_event(Transfer { + from: Some(*from), + to: Some(*to), + value, + }); + Ok(()) + } + } + + /// Unit tests. + #[cfg(test)] + mod tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + use ink::{ + env::hash::{ + Blake2x256, + CryptoHash, + HashOutput, + }, + primitives::Clear, + }; + + fn assert_transfer_event( + event: &ink::env::test::EmittedEvent, + expected_from: Option, + expected_to: Option, + expected_value: Balance, + ) { + let decoded_event = + ::decode(&mut &event.data[..]) + .expect("encountered invalid contract event data buffer"); + let Transfer { from, to, value } = decoded_event; + assert_eq!(from, expected_from, "encountered invalid Transfer.from"); + assert_eq!(to, expected_to, "encountered invalid Transfer.to"); + assert_eq!(value, expected_value, "encountered invalid Trasfer.value"); + + fn encoded_into_hash(entity: T) -> Hash + where + T: ink::scale::Encode, + { + let mut result = Hash::CLEAR_HASH; + let len_result = result.as_ref().len(); + let encoded = entity.encode(); + let len_encoded = encoded.len(); + if len_encoded <= len_result { + result.as_mut()[..len_encoded].copy_from_slice(&encoded); + return result + } + let mut hash_output = + <::Type as Default>::default(); + ::hash(&encoded, &mut hash_output); + let copy_len = core::cmp::min(hash_output.len(), len_result); + result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]); + result + } + + let mut expected_topics = Vec::new(); + expected_topics.push( + ink::blake2x256!("Transfer(Option,Option,Balance)") + .into(), + ); + if let Some(from) = expected_from { + expected_topics.push(encoded_into_hash(from)); + } else { + expected_topics.push(Hash::CLEAR_HASH); + } + if let Some(to) = expected_to { + expected_topics.push(encoded_into_hash(to)); + } else { + expected_topics.push(Hash::CLEAR_HASH); + } + expected_topics.push(encoded_into_hash(value)); + + for (n, (actual_topic, expected_topic)) in + event.topics.iter().zip(expected_topics).enumerate() + { + let topic = ::decode(&mut &actual_topic[..]) + .expect("encountered invalid topic encoding"); + assert_eq!(topic, expected_topic, "encountered invalid topic at {n}"); + } + } + + /// The default constructor does its job. + #[ink::test] + fn new_works() { + // Constructor works. + let initial_supply = 100; + let erc20 = Erc20::new(initial_supply); + + // The `BaseErc20` trait has indeed been implemented. + assert_eq!(::total_supply(&erc20), initial_supply); + + // Transfer event triggered during initial construction. + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + } + + /// The total supply was applied. + #[ink::test] + fn total_supply_works() { + // Constructor works. + let initial_supply = 100; + let erc20 = Erc20::new(initial_supply); + // Transfer event triggered during initial construction. + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + // Get the token total supply. + assert_eq!(erc20.total_supply(), 100); + } + + /// Get the actual balance of an account. + #[ink::test] + fn balance_of_works() { + // Constructor works + let initial_supply = 100; + let erc20 = Erc20::new(initial_supply); + // Transfer event triggered during initial construction + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + let accounts = + ink::env::test::default_accounts::(); + // Alice owns all the tokens on contract instantiation + assert_eq!(erc20.balance_of(accounts.alice), 100); + // Bob does not owns tokens + assert_eq!(erc20.balance_of(accounts.bob), 0); + } + + #[ink::test] + fn transfer_works() { + // Constructor works. + let initial_supply = 100; + let mut erc20 = Erc20::new(initial_supply); + // Transfer event triggered during initial construction. + let accounts = + ink::env::test::default_accounts::(); + + assert_eq!(erc20.balance_of(accounts.bob), 0); + // Alice transfers 10 tokens to Bob. + assert_eq!(erc20.transfer(accounts.bob, 10), Ok(())); + // Bob owns 10 tokens. + assert_eq!(erc20.balance_of(accounts.bob), 10); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + // Check first transfer event related to ERC-20 instantiation. + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + // Check the second transfer event relating to the actual trasfer. + assert_transfer_event( + &emitted_events[1], + Some(AccountId::from([0x01; 32])), + Some(AccountId::from([0x02; 32])), + 10, + ); + } + + #[ink::test] + fn invalid_transfer_should_fail() { + // Constructor works. + let initial_supply = 100; + let mut erc20 = Erc20::new(initial_supply); + let accounts = + ink::env::test::default_accounts::(); + + assert_eq!(erc20.balance_of(accounts.bob), 0); + // Set Bob as caller + set_caller(accounts.bob); + + // Bob fails to transfers 10 tokens to Eve. + assert_eq!( + erc20.transfer(accounts.eve, 10), + Err(Error::InsufficientBalance) + ); + // Alice owns all the tokens. + assert_eq!(erc20.balance_of(accounts.alice), 100); + assert_eq!(erc20.balance_of(accounts.bob), 0); + assert_eq!(erc20.balance_of(accounts.eve), 0); + + // Transfer event triggered during initial construction. + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 1); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + } + + #[ink::test] + fn transfer_from_works() { + // Constructor works. + let initial_supply = 100; + let mut erc20 = Erc20::new(initial_supply); + // Transfer event triggered during initial construction. + let accounts = + ink::env::test::default_accounts::(); + + // Bob fails to transfer tokens owned by Alice. + assert_eq!( + erc20.transfer_from(accounts.alice, accounts.eve, 10), + Err(Error::InsufficientAllowance) + ); + // Alice approves Bob for token transfers on her behalf. + assert_eq!(erc20.approve(accounts.bob, 10), Ok(())); + + // The approve event takes place. + assert_eq!(ink::env::test::recorded_events().count(), 2); + + // Set Bob as caller. + set_caller(accounts.bob); + + // Bob transfers tokens from Alice to Eve. + assert_eq!( + erc20.transfer_from(accounts.alice, accounts.eve, 10), + Ok(()) + ); + // Eve owns tokens. + assert_eq!(erc20.balance_of(accounts.eve), 10); + + // Check all transfer events that happened during the previous calls: + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 3); + assert_transfer_event( + &emitted_events[0], + None, + Some(AccountId::from([0x01; 32])), + 100, + ); + // The second event `emitted_events[1]` is an Approve event that we skip + // checking. + assert_transfer_event( + &emitted_events[2], + Some(AccountId::from([0x01; 32])), + Some(AccountId::from([0x05; 32])), + 10, + ); + } + + #[ink::test] + fn allowance_must_not_change_on_failed_transfer() { + let initial_supply = 100; + let mut erc20 = Erc20::new(initial_supply); + let accounts = + ink::env::test::default_accounts::(); + + // Alice approves Bob for token transfers on her behalf. + let alice_balance = erc20.balance_of(accounts.alice); + let initial_allowance = alice_balance + 2; + assert_eq!(erc20.approve(accounts.bob, initial_allowance), Ok(())); + + // Set Bob as caller. + set_caller(accounts.bob); + + // Bob tries to transfer tokens from Alice to Eve. + let emitted_events_before = ink::env::test::recorded_events(); + assert_eq!( + erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1), + Err(Error::InsufficientBalance) + ); + // Allowance must have stayed the same + assert_eq!( + erc20.allowance(accounts.alice, accounts.bob), + initial_allowance + ); + // No more events must have been emitted + let emitted_events_after = ink::env::test::recorded_events(); + assert_eq!(emitted_events_before.count(), emitted_events_after.count()); + } + + fn set_caller(sender: AccountId) { + ink::env::test::set_caller::(sender); + } + } +} diff --git a/integration-tests/trait-flipper/.gitignore b/integration-tests/trait-flipper/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/trait-flipper/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-flipper/Cargo.toml b/integration-tests/trait-flipper/Cargo.toml new file mode 100644 index 00000000000..02c1b0d002a --- /dev/null +++ b/integration-tests/trait-flipper/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "trait_flipper" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/trait-flipper/lib.rs b/integration-tests/trait-flipper/lib.rs new file mode 100644 index 00000000000..7be1ac641ed --- /dev/null +++ b/integration-tests/trait-flipper/lib.rs @@ -0,0 +1,66 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::trait_definition] +pub trait Flip { + /// Flips the current value of the Flipper's boolean. + #[ink(message)] + fn flip(&mut self); + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + fn get(&self) -> bool; +} + +#[ink::contract] +pub mod flipper { + use super::Flip; + + #[ink(storage)] + pub struct Flipper { + value: bool, + } + + impl Flipper { + /// Creates a new flipper smart contract initialized to `false`. + #[ink(constructor)] + pub fn new() -> Self { + Self { + value: Default::default(), + } + } + } + + impl Flip for Flipper { + #[ink(message)] + fn flip(&mut self) { + self.value = !self.value; + } + + #[ink(message)] + fn get(&self) -> bool { + self.value + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + let flipper = Flipper::new(); + assert!(!flipper.get()); + } + + #[ink::test] + fn it_works() { + let mut flipper = Flipper::new(); + // Can call using universal call syntax using the trait. + assert!(!::get(&flipper)); + ::flip(&mut flipper); + // Normal call syntax possible to as long as the trait is in scope. + assert!(flipper.get()); + } + } +} diff --git a/integration-tests/trait-incrementer/.gitignore b/integration-tests/trait-incrementer/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/trait-incrementer/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-incrementer/Cargo.toml b/integration-tests/trait-incrementer/Cargo.toml new file mode 100644 index 00000000000..360a11ac2d8 --- /dev/null +++ b/integration-tests/trait-incrementer/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "trait-incrementer" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +traits = { path = "./traits", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "traits/std", +] +ink-as-dependency = [] diff --git a/integration-tests/trait-incrementer/lib.rs b/integration-tests/trait-incrementer/lib.rs new file mode 100644 index 00000000000..65edd6f9d16 --- /dev/null +++ b/integration-tests/trait-incrementer/lib.rs @@ -0,0 +1,70 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +pub mod incrementer { + use traits::{ + Increment, + Reset, + }; + + /// A concrete incrementer smart contract. + #[ink(storage)] + pub struct Incrementer { + value: u64, + } + + impl Incrementer { + /// Creates a new incrementer smart contract initialized with zero. + #[ink(constructor)] + pub fn new(init_value: u64) -> Self { + Self { value: init_value } + } + + /// Increases the value of the incrementer by an amount. + #[ink(message)] + pub fn inc_by(&mut self, delta: u64) { + self.value = self.value.checked_add(delta).unwrap(); + } + } + + impl Increment for Incrementer { + #[ink(message)] + fn inc(&mut self) { + self.inc_by(1) + } + + #[ink(message)] + fn get(&self) -> u64 { + self.value + } + } + + impl Reset for Incrementer { + #[ink(message)] + fn reset(&mut self) { + self.value = 0; + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn default_works() { + let incrementer = Incrementer::new(0); + assert_eq!(incrementer.get(), 0); + } + + #[test] + fn it_works() { + let mut incrementer = Incrementer::new(0); + // Can call using universal call syntax using the trait. + assert_eq!(::get(&incrementer), 0); + ::inc(&mut incrementer); + // Normal call syntax possible to as long as the trait is in scope. + assert_eq!(incrementer.get(), 1); + } + } +} diff --git a/integration-tests/trait-incrementer/traits/.gitignore b/integration-tests/trait-incrementer/traits/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/trait-incrementer/traits/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/trait-incrementer/traits/Cargo.toml b/integration-tests/trait-incrementer/traits/Cargo.toml new file mode 100644 index 00000000000..be488246a3e --- /dev/null +++ b/integration-tests/trait-incrementer/traits/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "traits" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[lib] +name = "traits" +path = "lib.rs" +crate-type = ["rlib"] + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/trait-incrementer/traits/lib.rs b/integration-tests/trait-incrementer/traits/lib.rs new file mode 100644 index 00000000000..23cc0244a1d --- /dev/null +++ b/integration-tests/trait-incrementer/traits/lib.rs @@ -0,0 +1,37 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +//! Traits are extracted into a separate crate to show how the user can import +//! several foreign traits and implement those for the contract. + +/// Allows to increment and get the current value. +#[ink::trait_definition] +pub trait Increment { + /// Increments the current value of the implementer by one (1). + #[ink(message)] + fn inc(&mut self); + + /// Returns the current value of the implementer. + #[ink(message)] + fn get(&self) -> u64; +} + +/// Allows to reset the current value. +#[ink::trait_definition] +pub trait Reset { + /// Resets the current value to zero. + #[ink(message)] + fn reset(&mut self); +} diff --git a/integration-tests/upgradeable-contracts/README.md b/integration-tests/upgradeable-contracts/README.md new file mode 100644 index 00000000000..85bd7b5c81f --- /dev/null +++ b/integration-tests/upgradeable-contracts/README.md @@ -0,0 +1,36 @@ +# Upgradeable Contracts + +There are different ways a contract can be upgraded in ink! + +This folder illustrates some of the common and best practices to achieve upgradeability in your contracts. + +## [`set-code-hash`](set-code-hash/) + +ink! provides an ability to replace the code under the given contract's address. +This is exactly what `set_code_hash()` function does. + +However, developers needs to be mindful of storage compatibility. +You can read more about storage compatibility on [use.ink](https://use.ink/basics/upgradeable-contracts#replacing-contract-code-with-set_code_hash) + +## [Delegator](delegator/) + +Delegator patter is based around a low level cross contract call function `delegate_call`. +It allows a contract to delegate its execution to some on-chain uploaded code. + +It is different from a traditional cross-contract call +because the call is delegate to the **code**, not the contract. + +Similarly, the storage compatibility issue is also applicable here. +However, there are certain nuances associated with using `delegate_call`. + +First of all, as demonstrated in the example, if the delegated code intends to mutate the caller's storage, +a developer needs to be mindful. If the delegated code modifies layout-full storage +(i.e. it contains at least non-`Lazy`, non-`Mapping` field), the `CallFlags::TAIL_CALL` flag needs to be specified and the storage layouts must match. +This is due to the way ink! execution call stack is operated +(see [Stack Exchange Answer](https://substrate.stackexchange.com/a/3352/3098) for more explanation). + +If the delegated code only modifies `Lazy` or `Mapping` field, the keys must be identical and `CallFlags::TAIL_CALL` is optional. +This is because `Lazy` and `Mapping` interact with the storage directly instead of loading and flushing storage states. + +If your storage is completely layoutless (it only contains `Lazy` and `Mapping` fields), the order of fields and layout do not need to match for the same reason as mentioned above. + diff --git a/integration-tests/upgradeable-contracts/delegator/.gitignore b/integration-tests/upgradeable-contracts/delegator/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/upgradeable-contracts/delegator/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/upgradeable-contracts/delegator/Cargo.toml b/integration-tests/upgradeable-contracts/delegator/Cargo.toml new file mode 100644 index 00000000000..ffcc3283363 --- /dev/null +++ b/integration-tests/upgradeable-contracts/delegator/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "delegator" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } +delegatee = { path = "delegatee", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore b/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml b/integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml new file mode 100644 index 00000000000..d18ce25dc3d --- /dev/null +++ b/integration-tests/upgradeable-contracts/delegator/delegatee/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "delegatee" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs b/integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs new file mode 100644 index 00000000000..af761b5bd55 --- /dev/null +++ b/integration-tests/upgradeable-contracts/delegator/delegatee/lib.rs @@ -0,0 +1,43 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod delegatee { + use ink::storage::{ + traits::ManualKey, + Mapping, + }; + #[ink(storage)] + pub struct Delegatee { + addresses: Mapping>, + counter: i32, + // Uncommenting below line will break storage compatibility. + // flag: bool, + } + + impl Delegatee { + /// When using the delegate call. You only upload the code of the delegatee + /// contract. However, the code and storage do not get initialized. + /// + /// Because of this. The constructor actually never gets called. + #[allow(clippy::new_without_default)] + #[ink(constructor)] + pub fn new() -> Self { + unreachable!( + "Constructors are not called when upgrading using `set_code_hash`." + ) + } + + /// Increments the current value. + #[ink(message)] + pub fn inc(&mut self) { + self.counter = self.counter.checked_add(2).unwrap(); + } + + /// Adds current value of counter to the `addresses` + #[ink(message)] + pub fn append_address_value(&mut self) { + let caller = self.env().caller(); + self.addresses.insert(caller, &self.counter); + } + } +} diff --git a/integration-tests/upgradeable-contracts/delegator/lib.rs b/integration-tests/upgradeable-contracts/delegator/lib.rs new file mode 100644 index 00000000000..b6d95c3a1ca --- /dev/null +++ b/integration-tests/upgradeable-contracts/delegator/lib.rs @@ -0,0 +1,208 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod delegator { + use ink::{ + env::CallFlags, + storage::Mapping, + }; + + use ink::{ + env::{ + call::{ + build_call, + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }, + storage::traits::ManualKey, + }; + + #[ink(storage)] + pub struct Delegator { + addresses: Mapping>, + counter: i32, + } + + impl Delegator { + /// Creates a new delegator smart contract initialized with the given value. + #[ink(constructor)] + pub fn new(init_value: i32) -> Self { + let v = Mapping::new(); + Self { + addresses: v, + counter: init_value, + } + } + + /// Creates a new contract with default values. + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + /// Increment the current value using delegate call. + #[ink(message)] + pub fn inc_delegate(&mut self, hash: Hash) { + let selector = ink::selector_bytes!("inc"); + let _ = build_call::() + .delegate(hash) + // We specify `CallFlags::TAIL_CALL` to use the delegatee last memory frame + // as the end of the execution cycle. + // So any mutations to `Packed` types, made by delegatee, + // will be flushed to storage. + // + // If we don't specify this flag. + // The storage state before the delegate call will be flushed to storage instead. + // See https://substrate.stackexchange.com/questions/3336/i-found-set-allow-reentry-may-have-some-problems/3352#3352 + .call_flags(CallFlags::TAIL_CALL) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::<()>() + .try_invoke(); + } + + /// Adds entry to `addresses` using delegate call. + /// Note that we don't need `CallFlags::TAIL_CALL` flag + /// because `Mapping` updates the storage instantly on-demand. + #[ink(message)] + pub fn add_entry_delegate(&mut self, hash: Hash) { + let selector = ink::selector_bytes!("append_address_value"); + let _ = build_call::() + .delegate(hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::<()>() + .try_invoke(); + } + + /// Returns the current value of the counter. + #[ink(message)] + pub fn get_counter(&self) -> i32 { + self.counter + } + + /// Returns the current value of the address. + #[ink(message)] + pub fn get_value(&self, address: AccountId) -> (AccountId, Option) { + (self.env().caller(), self.addresses.get(address)) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::{ + ChainBackend, + ContractsBackend, + }; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_counter_mutated( + mut client: Client, + ) -> E2EResult<()> { + // given + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let mut constructor = DelegatorRef::new_default(); + let call_builder = client + .instantiate("delegator", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("delegatee", &origin) + .submit() + .await + .expect("upload `delegatee` failed") + .code_hash; + + // when + let call_delegate = call_builder_call.inc_delegate(code_hash); + + let result = client.call(&origin, &call_delegate).submit().await; + assert!(result.is_ok(), "delegate call failed."); + + let result = client.call(&origin, &call_delegate).submit().await; + assert!(result.is_ok(), "second delegate call failed."); + + // then + let expected_value = 4; + let call = call_builder.call::(); + + let call_get = call.get_counter(); + let call_get_result = client + .call(&origin, &call_get) + .dry_run() + .await? + .return_value(); + + // This fails + assert_eq!( + call_get_result, expected_value, + "Decoded an unexpected value from the call." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_mapping_mutated( + mut client: Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + // given + let mut constructor = DelegatorRef::new(10); + let call_builder = client + .instantiate("delegator", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let code_hash = client + .upload("delegatee", &origin) + .submit() + .await + .expect("upload `delegatee` failed") + .code_hash; + + // when + let call_delegate = call_builder_call.add_entry_delegate(code_hash); + let result = client.call(&origin, &call_delegate).submit().await; + assert!(result.is_ok(), "delegate call failed."); + + // then + + // because we initialize the counter with `10` we expect this value be + // assigned to Alice. + let expected_value = 10; + // Alice's address + let address = AccountId::from(origin.public_key().to_account_id().0); + + let call_get_value = call_builder_call.get_value(address); + let call_get_result = client + .call(&origin, &call_get_value) + .submit() + .await + .unwrap() + .return_value(); + + assert_eq!( + call_get_result, + (address, Some(expected_value)), + "Decoded an unexpected value from the call." + ); + + Ok(()) + } + } +} diff --git a/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml b/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml new file mode 100644 index 00000000000..8303a6dc3db --- /dev/null +++ b/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "incrementer" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/upgradeable-contracts/set-code-hash/lib.rs b/integration-tests/upgradeable-contracts/set-code-hash/lib.rs new file mode 100644 index 00000000000..ca8ec49f6d4 --- /dev/null +++ b/integration-tests/upgradeable-contracts/set-code-hash/lib.rs @@ -0,0 +1,138 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +//! Demonstrates how to use [`set_code_hash`](https://docs.rs/ink_env/latest/ink_env/fn.set_code_hash.html) +//! to swap out the `code_hash` of an on-chain contract. +//! +//! We will swap the code of our `Incrementer` contract with that of the an `Incrementer` +//! found in the `updated_incrementer` folder. +//! +//! See the included End-to-End tests an example update workflow. + +#[ink::contract] +pub mod incrementer { + + /// Track a counter in storage. + /// + /// # Note + /// + /// Is is important to realize that after the call to `set_code_hash` the contract's + /// storage remains the same. + /// + /// If you change the storage layout in your storage struct you may introduce + /// undefined behavior to your contract! + #[ink(storage)] + #[derive(Default)] + pub struct Incrementer { + count: u32, + } + + impl Incrementer { + /// Creates a new counter smart contract initialized with the given base value. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Increments the counter value which is stored in the contract's storage. + #[ink(message)] + pub fn inc(&mut self) { + self.count = self.count.checked_add(1).unwrap(); + ink::env::debug_println!( + "The new count is {}, it was modified using the original contract code.", + self.count + ); + } + + /// Returns the counter value which is stored in this contract's storage. + #[ink(message)] + pub fn get(&self) -> u32 { + self.count + } + + /// Modifies the code which is used to execute calls to this contract address + /// (`AccountId`). + /// + /// We use this to upgrade the contract logic. We don't do any authorization here, + /// any caller can execute this method. + /// + /// In a production contract you would do some authorization here! + #[ink(message)] + pub fn set_code(&mut self, code_hash: Hash) { + self.env().set_code_hash(&code_hash).unwrap_or_else(|err| { + panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") + }); + ink::env::debug_println!("Switched code hash to {:?}.", code_hash); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test(additional_contracts = "./updated-incrementer/Cargo.toml")] + async fn set_code_works(mut client: Client) -> E2EResult<()> { + // Given + let mut constructor = IncrementerRef::new(); + let contract = client + .instantiate("incrementer", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let get = call.get(); + let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; + assert!(matches!(get_res.return_value(), 0)); + + let inc = call.inc(); + let _inc_result = client + .call(&ink_e2e::alice(), &inc) + .submit() + .await + .expect("`inc` failed"); + + let get = call.get(); + let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; + assert!(matches!(get_res.return_value(), 1)); + + // When + let new_code_hash = client + .upload("updated_incrementer", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `updated_incrementer` failed") + .code_hash; + + let new_code_hash = new_code_hash.as_ref().try_into().unwrap(); + let set_code = call.set_code(new_code_hash); + + let _set_code_result = client + .call(&ink_e2e::alice(), &set_code) + .submit() + .await + .expect("`set_code` failed"); + + // Then + // Note that our contract's `AccountId` (so `contract_acc_id`) has stayed the + // same between updates! + let inc = call.inc(); + + let _inc_result = client + .call(&ink_e2e::alice(), &inc) + .submit() + .await + .expect("`inc` failed"); + + let get = call.get(); + let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; + + // Remember, we updated our incrementer contract to increment by `4`. + assert!(matches!(get_res.return_value(), 5)); + + Ok(()) + } + } +} diff --git a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml new file mode 100644 index 00000000000..128a54b2a95 --- /dev/null +++ b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "updated-incrementer" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs new file mode 100644 index 00000000000..7741181a20e --- /dev/null +++ b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs @@ -0,0 +1,69 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +pub mod incrementer { + + /// Track a counter in storage. + /// + /// # Note + /// + /// We have kept the same storage layout as in our original `incrementer` contract. + /// + /// Had we changed `count` to, for example, an `AccountId` we would end up with + /// undefined behaviour in our contract. + #[ink(storage)] + pub struct Incrementer { + count: u32, + } + + impl Incrementer { + /// Creates a new counter smart contract initialized with the given base value. + /// + /// # Note + /// + /// When upgrading using the `set_code_hash` workflow we only need to point to a + /// contract's uploaded code hash, **not** an instantiated contract's + /// `AccountId`. + /// + /// Because of this we will never actually call the constructor of this contract. + #[ink(constructor)] + pub fn new() -> Self { + unreachable!( + "Constructors are not called when upgrading using `set_code_hash`." + ) + } + + /// Increments the counter value which is stored in the contract's storage. + /// + /// # Note + /// + /// We use a different step size (4) here than in the original `incrementer`. + #[ink(message)] + pub fn inc(&mut self) { + self.count = self.count.checked_add(4).unwrap(); + ink::env::debug_println!("The new count is {}, it was modified using the updated `new_incrementer` code.", self.count); + } + + /// Returns the counter value which is stored in this contract's storage. + #[ink(message)] + pub fn get(&self) -> u32 { + self.count + } + + /// Modifies the code which is used to execute calls to this contract address + /// (`AccountId`). + /// + /// We use this to upgrade the contract logic. We don't do any authorization here, + /// any caller can execute this method. + /// + /// In a production contract you would do some authorization here! + #[ink(message)] + pub fn set_code(&mut self, code_hash: Hash) { + self.env().set_code_hash(&code_hash).unwrap_or_else(|err| { + panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") + }); + ink::env::debug_println!("Switched code hash to {:?}.", code_hash); + } + } +} diff --git a/integration-tests/wildcard-selector/.gitignore b/integration-tests/wildcard-selector/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/wildcard-selector/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/wildcard-selector/Cargo.toml b/integration-tests/wildcard-selector/Cargo.toml new file mode 100644 index 00000000000..e8db7c0101a --- /dev/null +++ b/integration-tests/wildcard-selector/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "wildcard-selector" +version = "5.0.0-rc" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/wildcard-selector/lib.rs b/integration-tests/wildcard-selector/lib.rs new file mode 100644 index 00000000000..1e99545ba34 --- /dev/null +++ b/integration-tests/wildcard-selector/lib.rs @@ -0,0 +1,163 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod wildcard_selector { + use ink::prelude::string::String; + + #[ink(storage)] + pub struct WildcardSelector {} + + impl WildcardSelector { + /// Creates a new wildcard selector smart contract. + #[ink(constructor)] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self {} + } + + /// Wildcard selector handles messages with any selector. + #[ink(message, selector = _)] + pub fn wildcard(&mut self) { + let (_selector, _message) = + ink::env::decode_input::<([u8; 4], String)>().unwrap(); + ink::env::debug_println!( + "Wildcard selector: {:?}, message: {}", + _selector, + _message + ); + } + + /// Wildcard complement handles messages with a well-known reserved selector. + #[ink(message, selector = @)] + pub fn wildcard_complement(&mut self, _message: String) { + ink::env::debug_println!("Wildcard complement message: {}", _message); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + use ink::env::call::utils::{ + Argument, + ArgumentList, + EmptyArgumentList, + }; + + type E2EResult = std::result::Result>; + type Environment = ::Env; + + fn build_message( + account_id: AccountId, + selector: [u8; 4], + message: String, + ) -> ink_e2e::CallBuilderFinal< + Environment, + ArgumentList, EmptyArgumentList>, + (), + > { + ink::env::call::build_call::() + .call(account_id) + .exec_input( + ink::env::call::ExecutionInput::new(ink::env::call::Selector::new( + selector, + )) + .push_arg(message), + ) + .returns::<()>() + } + + #[ink_e2e::test] + async fn arbitrary_selectors_handled_by_wildcard( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = WildcardSelectorRef::new(); + let contract_acc_id = client + .instantiate("wildcard_selector", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed") + .account_id; + + // when + const ARBITRARY_SELECTOR: [u8; 4] = [0xF9, 0xF9, 0xF9, 0xF9]; + let wildcard_message = "WILDCARD_MESSAGE 1".to_string(); + let wildcard = build_message( + contract_acc_id, + ARBITRARY_SELECTOR, + wildcard_message.clone(), + ); + + let result = client + .call(&ink_e2e::bob(), &wildcard) + .submit() + .await + .expect("wildcard failed"); + + const ARBITRARY_SELECTOR_2: [u8; 4] = [0x01, 0x23, 0x45, 0x67]; + let wildcard_message2 = "WILDCARD_MESSAGE 2".to_string(); + let wildcard2 = build_message( + contract_acc_id, + ARBITRARY_SELECTOR_2, + wildcard_message2.clone(), + ); + + let result2 = client + .call(&ink_e2e::bob(), &wildcard2) + .submit() + .await + .expect("wildcard failed"); + + // then + assert!(result.debug_message().contains(&format!( + "Wildcard selector: {:?}, message: {}", + ARBITRARY_SELECTOR, wildcard_message + ))); + + assert!(result2.debug_message().contains(&format!( + "Wildcard selector: {:?}, message: {}", + ARBITRARY_SELECTOR_2, wildcard_message2 + ))); + + Ok(()) + } + + #[ink_e2e::test] + async fn wildcard_complement_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = WildcardSelectorRef::new(); + let contract_acc_id = client + .instantiate("wildcard_selector", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed") + .account_id; + + // when + let wildcard_complement_message = "WILDCARD COMPLEMENT MESSAGE".to_string(); + let wildcard = build_message( + contract_acc_id, + ink::IIP2_WILDCARD_COMPLEMENT_SELECTOR, + wildcard_complement_message.clone(), + ); + + let result = client + .call(&ink_e2e::bob(), &wildcard) + .submit() + .await + .expect("wildcard failed"); + + // then + assert!(result.debug_message().contains(&format!( + "Wildcard complement message: {}", + wildcard_complement_message + ))); + + Ok(()) + } + } +} From a04859ebaf389af3cb35788ca0ed007f72a370f6 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 15:43:52 +0100 Subject: [PATCH 20/26] Use newer Docker image --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f4e10c3bf2..8fb5ebf364b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ on: - 'FILE_HEADER' env: - IMAGE: paritytech/ci-unified:bullseye-1.74.0 + IMAGE: paritytech/ci-unified:bullseye-1.75.0 CARGO_TARGET_DIR: /ci-cache/${{ github.repository }}/targets/${{ github.ref_name }}/${{ github.job }} CARGO_INCREMENTAL: 0 PURELY_STD_CRATES: ink/codegen metadata engine e2e e2e/macro ink/ir From 4975b786f2038adef70a027fe21b98e0817b2f6b Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 15:56:45 +0100 Subject: [PATCH 21/26] Improve variable name --- .github/workflows/ci.yml | 2 +- integration-tests/flipper/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fb5ebf364b..63f7de69571 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -491,7 +491,7 @@ jobs: # run flipper E2E test with on-chain contract substrate-contracts-node -lruntime::contracts=debug 2>&1 & cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml - export CONTRACT_HEX=$(cargo contract instantiate \ + export CONTRACT_ADDR_HEX=$(cargo contract instantiate \ --manifest-path integration-tests/flipper/Cargo.toml \ --suri //Alice --args true -x -y --output-json | \ jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index 83a1a8377ab..da51de882cd 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -144,7 +144,7 @@ pub mod flipper { mut client: Client, ) -> E2EResult<()> { // given - let addr = std::env::var("CONTRACT_HEX").unwrap().replace("0x", ""); + let addr = std::env::var("CONTRACT_ADDR_HEX").unwrap().replace("0x", ""); let acc_id = hex::decode(addr).unwrap(); let acc_id = AccountId::try_from(&acc_id[..]).unwrap(); From 703dcb253110c870ad1d47ca6cbd53dffc90381a Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 16:34:05 +0100 Subject: [PATCH 22/26] Update UI tests for new Rust version from Docker --- .../fail/impl-block-for-non-storage-01.stderr | 12 ++++++++++++ .../impl-block-using-static-env-no-marker.stderr | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr b/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr index b5dba1afb28..afe52ca0748 100644 --- a/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr +++ b/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr @@ -25,6 +25,12 @@ error[E0599]: no function or associated item named `constructor_2` found for str | | |function or associated item not found in `Contract` | |_______________|help: there is an associated function with a similar name: `constructor_1` | + | +note: if you're trying to build a new `Contract`, consider using `contract::_::::constructor_1` which returns `Contract` + --> tests/ui/contract/fail/impl-block-for-non-storage-01.rs:8:9 + | +8 | pub fn constructor_1() -> Self { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0599]: no function or associated item named `message_2` found for struct `Contract` in the current scope --> tests/ui/contract/fail/impl-block-for-non-storage-01.rs:25:16 @@ -44,3 +50,9 @@ error[E0599]: no function or associated item named `message_2` found for struct | | |function or associated item not found in `Contract` | |_______________|help: there is a method with a similar name: `message_1` | + | +note: if you're trying to build a new `Contract`, consider using `contract::_::::constructor_1` which returns `Contract` + --> tests/ui/contract/fail/impl-block-for-non-storage-01.rs:8:9 + | +8 | pub fn constructor_1() -> Self { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr b/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr index 2be15ec8121..82ca51e2862 100644 --- a/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr +++ b/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr @@ -7,6 +7,16 @@ error[E0599]: no function or associated item named `env` found for struct `Contr 20 | let _ = Self::env().caller(); | ^^^ function or associated item not found in `Contract` | +note: if you're trying to build a new `Contract` consider using one of the following associated functions: + contract::_::::constructor + Contract::constructor_impl + --> tests/ui/contract/fail/impl-block-using-static-env-no-marker.rs:8:9 + | +8 | pub fn constructor() -> Self { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +19 | fn constructor_impl() -> Self { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: items from traits can only be used if the trait is in scope help: the following trait is implemented but not in scope; perhaps add a `use` for it: | From 823ec1476d5bbdbc2d8af13d9e688fd9dc0c8bd6 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 16:35:09 +0100 Subject: [PATCH 23/26] Improve structure --- .github/workflows/ci.yml | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63f7de69571..8660aeb36f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -487,24 +487,28 @@ jobs: # run all tests with --all-features, which will run the e2e-tests feature if present scripts/for_all_contracts_exec.sh --path integration-tests --ignore static-buffer -- cargo test \ --verbose --all-features --manifest-path {} - - # run flipper E2E test with on-chain contract - substrate-contracts-node -lruntime::contracts=debug 2>&1 & - cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml - export CONTRACT_ADDR_HEX=$(cargo contract instantiate \ - --manifest-path integration-tests/flipper/Cargo.toml \ - --suri //Alice --args true -x -y --output-json | \ - jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) - CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test \ - --features e2e-tests \ - --manifest-path integration-tests/flipper/Cargo.toml \ - e2e_test_deployed_contract \ - -- --ignored --nocapture - # run the static buffer test with a custom buffer size cargo clean --manifest-path integration-tests/static-buffer/Cargo.toml INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path integration-tests/static-buffer/Cargo.toml --all-features + - name: Run E2E test with on-chain contract + env: + # Fix linking of `linkme`: https://github.com/dtolnay/linkme/issues/49 + RUSTFLAGS: -Clink-arg=-z -Clink-arg=nostart-stop-gc + run: | + # run flipper E2E test with on-chain contract + substrate-contracts-node -lruntime::contracts=debug 2>&1 & + cargo contract build --release --manifest-path integration-tests/flipper/Cargo.toml + export CONTRACT_ADDR_HEX=$(cargo contract instantiate \ + --manifest-path integration-tests/flipper/Cargo.toml \ + --suri //Alice --args true -x -y --output-json | \ + jq -r .contract | xargs subkey inspect | grep -o "0x.*" | head -n1) + CONTRACTS_NODE_URL=ws://127.0.0.1:9944 cargo test \ + --features e2e-tests \ + --manifest-path integration-tests/flipper/Cargo.toml \ + e2e_test_deployed_contract \ + -- --ignored --nocapture + examples-contract-build: runs-on: ubuntu-latest needs: [set-image, build] From 7c21503161902ea034f2007bc5d6aadc6a3388c5 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 16:43:19 +0100 Subject: [PATCH 24/26] Update UI tests for new Rust version from Docker --- .../fail/collections_only_packed_1.stderr | 45 +++++++++++++++++++ .../fail/collections_only_packed_2.stderr | 42 +++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr index 00902e1fed5..75dbb12921c 100644 --- a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr +++ b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr @@ -114,3 +114,48 @@ note: required by a bound in `Result` | pub enum Result { | ^ required by this bound in `Result` = note: this error originates in the derive macro `::ink::storage::traits::Storable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Vec: ink::parity_scale_codec::Decode` is not satisfied + --> tests/ui/storage_item/fail/collections_only_packed_1.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ the trait `ink::parity_scale_codec::Decode` is not implemented for `Vec` + | + = help: the trait `ink::parity_scale_codec::Decode` is implemented for `Vec` + = note: required for `Vec` to implement `Packed` + = note: required for `Vec` to implement `StorableHint<()>` + = note: required for `Vec` to implement `AutoStorableHint>` +note: required because it appears within the type `Contract` + --> tests/ui/storage_item/fail/collections_only_packed_1.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ +note: required by a bound in `ink_storage::ink_storage_traits::StorableHint::Type` + --> $WORKSPACE/crates/storage/traits/src/storage.rs + | + | type Type: Storable; + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `StorableHint::Type` + +error[E0277]: the trait bound `[NonPacked]: Encode` is not satisfied + --> tests/ui/storage_item/fail/collections_only_packed_1.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ the trait `Encode` is not implemented for `[NonPacked]` + | + = help: the following other types implement trait `Encode`: + [T; N] + [T] + = note: required for `Vec` to implement `Encode` + = note: required for `Vec` to implement `Packed` + = note: required for `Vec` to implement `StorableHint<()>` + = note: required for `Vec` to implement `AutoStorableHint>` +note: required because it appears within the type `Contract` + --> tests/ui/storage_item/fail/collections_only_packed_1.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ +note: required by a bound in `ink_storage::ink_storage_traits::StorableHint::Type` + --> $WORKSPACE/crates/storage/traits/src/storage.rs + | + | type Type: Storable; + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `StorableHint::Type` diff --git a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr index b44e9672c74..33c98c16deb 100644 --- a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr +++ b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr @@ -105,3 +105,45 @@ note: required by a bound in `Result` | pub enum Result { | ^ required by this bound in `Result` = note: this error originates in the derive macro `::ink::storage::traits::Storable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `BTreeMap: ink::parity_scale_codec::Decode` is not satisfied + --> tests/ui/storage_item/fail/collections_only_packed_2.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ the trait `ink::parity_scale_codec::Decode` is not implemented for `BTreeMap` + | + = help: the trait `ink::parity_scale_codec::Decode` is implemented for `BTreeMap` + = note: required for `BTreeMap` to implement `Packed` + = note: required for `BTreeMap` to implement `StorableHint<()>` + = note: required for `BTreeMap` to implement `AutoStorableHint>` +note: required because it appears within the type `Contract` + --> tests/ui/storage_item/fail/collections_only_packed_2.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ +note: required by a bound in `ink_storage::ink_storage_traits::StorableHint::Type` + --> $WORKSPACE/crates/storage/traits/src/storage.rs + | + | type Type: Storable; + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `StorableHint::Type` + +error[E0277]: the trait bound `BTreeMap: Encode` is not satisfied + --> tests/ui/storage_item/fail/collections_only_packed_2.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ the trait `Encode` is not implemented for `BTreeMap` + | + = help: the trait `Encode` is implemented for `BTreeMap` + = note: required for `BTreeMap` to implement `Packed` + = note: required for `BTreeMap` to implement `StorableHint<()>` + = note: required for `BTreeMap` to implement `AutoStorableHint>` +note: required because it appears within the type `Contract` + --> tests/ui/storage_item/fail/collections_only_packed_2.rs:10:8 + | +10 | struct Contract { + | ^^^^^^^^ +note: required by a bound in `ink_storage::ink_storage_traits::StorableHint::Type` + --> $WORKSPACE/crates/storage/traits/src/storage.rs + | + | type Type: Storable; + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `StorableHint::Type` From b56979a87c71aba1bdff41c3b5e3ae0a1fdfe33e Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Fri, 26 Jan 2024 16:44:57 +0100 Subject: [PATCH 25/26] Make `rustfmt` happy --- integration-tests/flipper/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index da51de882cd..f799e303a47 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -144,7 +144,9 @@ pub mod flipper { mut client: Client, ) -> E2EResult<()> { // given - let addr = std::env::var("CONTRACT_ADDR_HEX").unwrap().replace("0x", ""); + let addr = std::env::var("CONTRACT_ADDR_HEX") + .unwrap() + .replace("0x", ""); let acc_id = hex::decode(addr).unwrap(); let acc_id = AccountId::try_from(&acc_id[..]).unwrap(); From 337d2982d70ab8802dc8be2fd69ee8991112c883 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 29 Jan 2024 14:05:18 +0100 Subject: [PATCH 26/26] Update image to 1.75 --- .github/workflows/measurements.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/measurements.yml b/.github/workflows/measurements.yml index 1c8d810ca65..5cea238d4c9 100644 --- a/.github/workflows/measurements.yml +++ b/.github/workflows/measurements.yml @@ -14,7 +14,7 @@ jobs: run: shell: bash container: - image: paritytech/ci-unified:bullseye-1.74.0 + image: paritytech/ci-unified:bullseye-1.75.0 steps: - name: Checkout uses: actions/checkout@v4