diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db2f7afe..113b4875 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,22 +52,20 @@ jobs: .vscode/** **/.gitignore **/*.md - multi-contract-caller/** upgradeable-contracts/** workspace-contracts/** ui/** **/frontend/** json: true - - name: Look for changes in multi and upgradable contracts - id: CHANGED_MULTI_AND_UPGRADEABLE_CONTRACTS + - name: Look for changes in upgradable contracts + id: CHANGED_UPGRADEABLE_CONTRACTS uses: tj-actions/changed-files@v41 with: dir_names: "true" dir_names_exclude_current_dir: "true" dir_names_max_depth: 2 files: | - multi-contract-caller/** upgradeable-contracts/** files_ignore: | **/.gitignore @@ -103,7 +101,7 @@ jobs: run: | ALL_CHANGED_CONTRACTS=$(jq -s 'add' \ <(echo "${{ steps.CHANGED_CONTRACTS.outputs.all_changed_files }}") \ - <(echo "${{ steps.CHANGED_MULTI_AND_UPGRADEABLE_CONTRACTS.outputs.all_changed_files }}")) + <(echo "${{ steps.CHANGED_UPGRADEABLE_CONTRACTS.outputs.all_changed_files }}")) if [ ${{steps.CHANGED_WORKSPACE_CONTRACTS.outputs.all_changed_files }} = "[]" ] then @@ -220,9 +218,15 @@ jobs: run: cargo contract build --verbose --manifest-path=${{ matrix.contract }}/Cargo.toml; - name: Test ${{ matrix.contract }} on ${{ matrix.platform }}-${{ matrix.toolchain }} - if: runner.os != 'Windows' && matrix.contract != 'workspace-contracts' + if: runner.os != 'Windows' && matrix.contract != 'workspace-contracts' && matrix.contract != 'static-buffer' run: cargo test --verbose --manifest-path=${{ matrix.contract }}/Cargo.toml; + - name: Test ${{ matrix.contract }} on ${{ matrix.platform }}-${{ matrix.toolchain }} + if: runner.os != 'Windows' && matrix.contract == 'static-buffer' + run: | + cargo clean --manifest-path=${{ matrix.contract }}/Cargo.toml + INK_STATIC_BUFFER_SIZE=30 cargo test --verbose --manifest-path=${{ matrix.contract }}/Cargo.toml + - name: Build workspace ${{ matrix.contract }} on ${{ matrix.platform }}-${{ matrix.toolchain }} if: runner.os != 'Windows' && matrix.contract == 'workspace-contracts' run: | diff --git a/README.md b/README.md index 573551be..8ceb6c73 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Have a look at the different examples to better understand how to use ink! to bu ### Can I add a new example here? -Please don't add them here, but create a Pull Request to the `integration-tests` folder in [the ink! repository](https://github.com/paritytech/ink) instead. +Please don't add them here, but create a Pull Request to the `integration-tests/public` folder in [the ink! repository](https://github.com/paritytech/ink) instead. The content of that folder is synchronized with this repository on new releases. ## Preparation diff --git a/basic-contract-caller/Cargo.toml b/basic-contract-caller/Cargo.toml index 25891292..f42e4d3c 100755 --- a/basic-contract-caller/Cargo.toml +++ b/basic-contract-caller/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "basic-contract-caller" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } # Note: We **need** to specify the `ink-as-dependency` feature. # @@ -14,7 +14,7 @@ ink = { version = "5.0.0-rc.1", default-features = false } other-contract = { path = "other-contract", default-features = false, features = ["ink-as-dependency"] } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/basic-contract-caller/other-contract/Cargo.toml b/basic-contract-caller/other-contract/Cargo.toml index 1a857e73..947bd1f7 100755 --- a/basic-contract-caller/other-contract/Cargo.toml +++ b/basic-contract-caller/other-contract/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "other-contract" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/call-runtime/Cargo.toml b/call-runtime/Cargo.toml index 5af188aa..6d2f9010 100644 --- a/call-runtime/Cargo.toml +++ b/call-runtime/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "call-runtime" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } # Substrate # @@ -18,7 +18,7 @@ sp-io = { version = "23.0.0", default-features = false, features = ["disable_pan sp-runtime = { version = "24.0.0", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/combined-extension/.gitignore b/combined-extension/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/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/combined-extension/Cargo.toml b/combined-extension/Cargo.toml new file mode 100755 index 00000000..2830764a --- /dev/null +++ b/combined-extension/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "combined_extension" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", 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/combined-extension/README.md b/combined-extension/README.md new file mode 100644 index 00000000..f71d8cba --- /dev/null +++ b/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/combined-extension/lib.rs b/combined-extension/lib.rs new file mode 100755 index 00000000..5c5cd76a --- /dev/null +++ b/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 instance that provides access to + /// 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/conditional-compilation/Cargo.toml b/conditional-compilation/Cargo.toml index 0510c150..f7c0d9b9 100755 --- a/conditional-compilation/Cargo.toml +++ b/conditional-compilation/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "conditional-compilation" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/contract-storage/.gitignore b/contract-storage/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/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/contract-storage/Cargo.toml b/contract-storage/Cargo.toml new file mode 100755 index 00000000..15606532 --- /dev/null +++ b/contract-storage/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "contract-storage" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/contract-storage/e2e_tests.rs b/contract-storage/e2e_tests.rs new file mode 100644 index 00000000..ea7f71db --- /dev/null +++ b/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_builder = contract.call_builder::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call_builder.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_builder = contract.call_builder::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call_builder.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_builder = contract.call_builder::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call_builder.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_builder = contract.call_builder::(); + + // when + let result = client + .call( + &ink_e2e::alice(), + &call_builder.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/contract-storage/lib.rs b/contract-storage/lib.rs new file mode 100755 index 00000000..1404711f --- /dev/null +++ b/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/contract-terminate/Cargo.toml b/contract-terminate/Cargo.toml index 3ac36e59..9f48c84c 100644 --- a/contract-terminate/Cargo.toml +++ b/contract-terminate/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "contract_terminate" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/contract-transfer/Cargo.toml b/contract-transfer/Cargo.toml index 010b4eab..d0710384 100644 --- a/contract-transfer/Cargo.toml +++ b/contract-transfer/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "contract_transfer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/upgradeable-contracts/delegator/delegatee/.gitignore b/cross-contract-calls/.gitignore similarity index 100% rename from upgradeable-contracts/delegator/delegatee/.gitignore rename to cross-contract-calls/.gitignore diff --git a/cross-contract-calls/Cargo.toml b/cross-contract-calls/Cargo.toml new file mode 100755 index 00000000..bc5acdff --- /dev/null +++ b/cross-contract-calls/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "cross-contract-calls" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", 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 = { version = "5.0.0-rc.2" } + +[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/cross-contract-calls/e2e_tests.rs b/cross-contract-calls/e2e_tests.rs new file mode 100644 index 00000000..ea66516d --- /dev/null +++ b/cross-contract-calls/e2e_tests.rs @@ -0,0 +1,179 @@ +use super::cross_contract_calls::*; +use ink_e2e::ContractsBackend; + +type E2EResult = std::result::Result>; + +#[ink_e2e::test] +async fn flip_and_get(mut client: Client) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = CrossContractCallsRef::new_v1(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("basic-contract-caller instantiate failed"); + let mut call_builder = contract.call_builder::(); + let call = call_builder.flip_and_get_v1(); + + // when + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get` failed") + .return_value(); + + assert!(!result); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_v2_with_insufficient_storage_deposit_limit( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_TIME_LIMIT: u64 = 100_000; + const STORAGE_DEPOSIT_LIMIT: u128 = 100_000_000_000; + + let mut constructor = CrossContractCallsRef::new_v2_with_limits( + other_contract_code.code_hash, + REF_TIME_LIMIT, + PROOF_TIME_LIMIT, + STORAGE_DEPOSIT_LIMIT, + ); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + let Err(ink_e2e::Error::InstantiateDryRun(err)) = contract else { + panic!("instantiate should have failed at the dry run"); + }; + + // insufficient storage deposit limit + assert!( + err.error + .to_string() + .contains("StorageDepositLimitExhausted"), + "should have failed with StorageDepositLimitExhausted" + ); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_v2_with_sufficient_limits( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_TIME_LIMIT: u64 = 100_000; + const STORAGE_DEPOSIT_LIMIT: u128 = 500_000_000_000; + + let mut constructor = CrossContractCallsRef::new_v2_with_limits( + other_contract_code.code_hash, + REF_TIME_LIMIT, + PROOF_TIME_LIMIT, + STORAGE_DEPOSIT_LIMIT, + ); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + assert!(contract.is_ok(), "{}", contract.err().unwrap()); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_v2_no_limits( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = + CrossContractCallsRef::new_v2_no_limits(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + assert!(contract.is_ok(), "{}", contract.err().unwrap()); + + Ok(()) +} + +#[ink_e2e::test] +async fn flip_and_get_v2(mut client: Client) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = CrossContractCallsRef::new_v1(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("cross-contract-calls instantiate failed"); + let mut call_builder = contract.call_builder::(); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_TIME_LIMIT: u64 = 100_000; + const STORAGE_DEPOSIT_LIMIT: u128 = 1_000_000_000; + + // when + let call = call_builder.flip_and_get_invoke_v2_with_limits( + REF_TIME_LIMIT, + PROOF_TIME_LIMIT, + STORAGE_DEPOSIT_LIMIT, + ); + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get` failed") + .return_value(); + + assert!(!result); + + let call = call_builder.flip_and_get_invoke_v2_no_weight_limit(); + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get` failed") + .return_value(); + + assert!(result); + + Ok(()) +} diff --git a/cross-contract-calls/lib.rs b/cross-contract-calls/lib.rs new file mode 100755 index 00000000..33561715 --- /dev/null +++ b/cross-contract-calls/lib.rs @@ -0,0 +1,116 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod cross_contract_calls { + use ink::codegen::TraitCallBuilder; + use other_contract::OtherContractRef; + + #[ink(storage)] + pub struct CrossContractCalls { + other_contract: OtherContractRef, + } + + impl CrossContractCalls { + /// Initializes the contract by instantiating the code at the given code hash via + /// `instantiate_v2` host function with the supplied weight and storage + /// limits. + #[ink(constructor)] + pub fn new_v2_with_limits( + other_contract_code_hash: Hash, + ref_time_limit: u64, + proof_time_limit: u64, + storage_deposit_limit: Balance, + ) -> Self { + let other_contract = OtherContractRef::new(true) + .code_hash(other_contract_code_hash) + .endowment(0) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .ref_time_limit(ref_time_limit) + .proof_time_limit(proof_time_limit) + .storage_deposit_limit(storage_deposit_limit) + .instantiate(); + + Self { other_contract } + } + + /// Initializes the contract by instantiating the code at the given code hash via + /// the `instantiate_v2` host function with no weight or storage limits. + #[ink(constructor)] + pub fn new_v2_no_limits(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 } + } + + /// Initializes the contract by instantiating the code at the given code hash via + /// the original `instantiate` host function. + #[ink(constructor)] + pub fn new_v1(other_contract_code_hash: Hash) -> Self { + let other_contract = OtherContractRef::new(true) + .instantiate_v1() + .code_hash(other_contract_code_hash) + .endowment(0) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate(); + + Self { other_contract } + } + + /// Basic invocation of the other contract via the contract reference. + /// + /// *Note* this will invoke the original `call` (V1) host function, which will be + /// deprecated in the future. + #[ink(message)] + pub fn flip_and_get_v1(&mut self) -> bool { + let call_builder = self.other_contract.call_mut(); + + call_builder.flip().call_v1().invoke(); + call_builder.get().call_v1().invoke() + } + + /// Use the new `call_v2` host function via the call builder to forward calls to + /// the other contract, initially calling `flip` and then `get` to return the + /// result. + /// + /// This demonstrates how to set the new weight and storage limit parameters via + /// the call builder API. + #[ink(message)] + pub fn flip_and_get_invoke_v2_with_limits( + &mut self, + ref_time_limit: u64, + proof_time_limit: u64, + storage_deposit_limit: Balance, + ) -> bool { + let call_builder = self.other_contract.call_mut(); + + call_builder + .flip() + .ref_time_limit(ref_time_limit) + .proof_time_limit(proof_time_limit) + .storage_deposit_limit(storage_deposit_limit) + .invoke(); + + call_builder + .get() + .ref_time_limit(ref_time_limit) + .proof_time_limit(proof_time_limit) + .storage_deposit_limit(storage_deposit_limit) + .invoke() + } + + /// Demonstrate that the `call_v2` succeeds without having specified the weight + /// and storage limit parameters + #[ink(message)] + pub fn flip_and_get_invoke_v2_no_weight_limit(&mut self) -> bool { + self.other_contract.flip(); + self.other_contract.get() + } + } +} + +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests; diff --git a/cross-contract-calls/other-contract/Cargo.toml b/cross-contract-calls/other-contract/Cargo.toml new file mode 100755 index 00000000..947bd1f7 --- /dev/null +++ b/cross-contract-calls/other-contract/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "other-contract" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/cross-contract-calls/other-contract/lib.rs b/cross-contract-calls/other-contract/lib.rs new file mode 100755 index 00000000..ed280b63 --- /dev/null +++ b/cross-contract-calls/other-contract/lib.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::other_contract::{ + OtherContract, + 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/custom-allocator/Cargo.toml b/custom-allocator/Cargo.toml index 0507cf13..7b9138e7 100755 --- a/custom-allocator/Cargo.toml +++ b/custom-allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom-allocator" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false @@ -8,13 +8,13 @@ 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 = { version = "5.0.0-rc.1", default-features = false, features = ["no-allocator"] } +ink = { version = "5.0.0-rc.2", 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 = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/custom-environment/Cargo.toml b/custom-environment/Cargo.toml index 073714e3..c155e634 100644 --- a/custom-environment/Cargo.toml +++ b/custom-environment/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "custom-environment" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/dns/Cargo.toml b/dns/Cargo.toml index 8239d851..e5c66422 100644 --- a/dns/Cargo.toml +++ b/dns/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "dns" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/e2e-call-runtime/Cargo.toml b/e2e-call-runtime/Cargo.toml index 753c63a6..8a5447f2 100644 --- a/e2e-call-runtime/Cargo.toml +++ b/e2e-call-runtime/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "e2e_call_runtime" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/erc1155/Cargo.toml b/erc1155/Cargo.toml index 932dca73..b08d5499 100644 --- a/erc1155/Cargo.toml +++ b/erc1155/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "erc1155" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/erc20/Cargo.toml b/erc20/Cargo.toml index 615040de..a8118a7d 100644 --- a/erc20/Cargo.toml +++ b/erc20/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "erc20" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/erc721/Cargo.toml b/erc721/Cargo.toml index 1177c4b1..5eeb8c11 100644 --- a/erc721/Cargo.toml +++ b/erc721/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "erc721" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/flipper/Cargo.toml b/flipper/Cargo.toml index f64a0e9e..2ef81829 100644 --- a/flipper/Cargo.toml +++ b/flipper/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "flipper" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } hex = { version = "0.4.3" } [lib] diff --git a/incrementer/Cargo.toml b/incrementer/Cargo.toml index deac5e7f..7643b5bf 100644 --- a/incrementer/Cargo.toml +++ b/incrementer/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "incrementer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/lazyvec/.gitignore b/lazyvec/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/lazyvec/.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/lazyvec/Cargo.toml b/lazyvec/Cargo.toml new file mode 100755 index 00000000..ab850b0c --- /dev/null +++ b/lazyvec/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "lazyvec-integration-tests" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/lazyvec/lib.rs b/lazyvec/lib.rs new file mode 100755 index 00000000..7c146808 --- /dev/null +++ b/lazyvec/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_builder = contract.call_builder::(); + + // when + let create = call_builder.create_proposal(vec![0x41], 5, 1); + let _ = client + .call(&ink_e2e::alice(), &create) + .submit() + .await + .expect("Calling `create_proposal` failed"); + + let approve = call_builder.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_builder.get(0)) + .dry_run() + .await + .expect("get trapped when it shouldn't") + .return_value(); + assert_eq!(value.unwrap().approvals, 2); + + Ok(()) + } + } +} diff --git a/mapping/.cargo/config.toml b/mapping/.cargo/config.toml new file mode 100644 index 00000000..fcfc9c88 --- /dev/null +++ b/mapping/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +# Makes testing the fallible storage methods more efficient +INK_STATIC_BUFFER_SIZE = "256" diff --git a/mapping/.gitignore b/mapping/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/mapping/.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/mapping/Cargo.toml b/mapping/Cargo.toml new file mode 100755 index 00000000..c6630f63 --- /dev/null +++ b/mapping/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mapping-integration-tests" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/mapping/lib.rs b/mapping/lib.rs new file mode 100755 index 00000000..ccf07cf8 --- /dev/null +++ b/mapping/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_builder = contract.call_builder::(); + + // when + let insert = call_builder.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_builder.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_builder = contract.call_builder::(); + + // when + let insert = call_builder.insert_balance(1_000); + let _ = client + .call(&ink_e2e::bob(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + // then + let contains = call_builder.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_builder = contract.call_builder::(); + + // when + let first_insert = call_builder.insert_balance(1_000); + let _ = client + .call(&ink_e2e::charlie(), &first_insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + let insert = call_builder.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_builder.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_builder = contract.call_builder::(); + + // when + let insert = call_builder.insert_balance(3_000); + let _ = client + .call(&ink_e2e::dave(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + let remove = call_builder.remove_balance(); + let _ = client + .call(&ink_e2e::dave(), &remove) + .submit() + .await + .expect("Calling `remove_balance` failed"); + + // then + let get = call_builder.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_builder = contract.call_builder::(); + + // when + let insert = call_builder.insert_balance(4_000); + let _ = client + .call(&ink_e2e::eve(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed") + .return_value(); + + let take = call_builder.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_builder.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_builder = contract.call_builder::(); + + // when the mapping value overgrows the buffer + let name = ink_e2e::ferdie().public_key().to_account_id().to_string(); + let insert = call_builder.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_builder.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/multi-contract-caller/Cargo.toml b/multi-contract-caller/Cargo.toml index 79feebff..903aba31 100644 --- a/multi-contract-caller/Cargo.toml +++ b/multi-contract-caller/Cargo.toml @@ -1,19 +1,19 @@ [package] name = "multi-contract-caller" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", 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 = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/multi-contract-caller/accumulator/Cargo.toml b/multi-contract-caller/accumulator/Cargo.toml index abf40e34..5a6c1b92 100644 --- a/multi-contract-caller/accumulator/Cargo.toml +++ b/multi-contract-caller/accumulator/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "accumulator" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/multi-contract-caller/adder/Cargo.toml b/multi-contract-caller/adder/Cargo.toml index 23c2989e..55cca22d 100644 --- a/multi-contract-caller/adder/Cargo.toml +++ b/multi-contract-caller/adder/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "adder" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } accumulator = { path = "../accumulator", default-features = false, features = ["ink-as-dependency"] } diff --git a/multi-contract-caller/subber/Cargo.toml b/multi-contract-caller/subber/Cargo.toml index 9c2c8376..267bab6b 100644 --- a/multi-contract-caller/subber/Cargo.toml +++ b/multi-contract-caller/subber/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "subber" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } accumulator = { path = "../accumulator", default-features = false, features = ["ink-as-dependency"] } diff --git a/multisig/Cargo.toml b/multisig/Cargo.toml index f79b5523..0064d04c 100755 --- a/multisig/Cargo.toml +++ b/multisig/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "multisig" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/payment-channel/Cargo.toml b/payment-channel/Cargo.toml index 5ece2d66..fda9412e 100755 --- a/payment-channel/Cargo.toml +++ b/payment-channel/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "payment_channel" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] hex-literal = { version = "0.4.1" } diff --git a/psp22-extension/Cargo.toml b/psp22-extension/Cargo.toml index 540d4de1..cbaa3c2f 100755 --- a/psp22-extension/Cargo.toml +++ b/psp22-extension/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "psp22_extension" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/rand-extension/Cargo.toml b/rand-extension/Cargo.toml index 88ce2150..c8e1f54e 100755 --- a/rand-extension/Cargo.toml +++ b/rand-extension/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "rand_extension" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/static-buffer/.gitignore b/static-buffer/.gitignore new file mode 100644 index 00000000..bf910de1 --- /dev/null +++ b/static-buffer/.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/static-buffer/Cargo.toml b/static-buffer/Cargo.toml new file mode 100644 index 00000000..6cc5d736 --- /dev/null +++ b/static-buffer/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "static-buffer" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" +doctest = false + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/static-buffer/README.md b/static-buffer/README.md new file mode 100644 index 00000000..eb4f5872 --- /dev/null +++ b/static-buffer/README.md @@ -0,0 +1,18 @@ +# Static buffer configuration demo + +This is a dummy contract illustrating how the [static buffer](/ARCHITECTURE.md#communication-with-the-pallet) +can be be configured using the environmental variables. + +Simply, run: +```bash +cargo clean +INK_STATIC_BUFFER_SIZE=30 cargo test -F e2e-tests +``` + +This will configure the buffer to have enough space to instantiate the contract, +but not enough space to retrieve the caller's address as it is of 32 bytes, +but we only allocated 30 bytes to the contract. + +## Note +You must run `cargo clean` every time you want to modify the buffer size +because the value is baked into the binaries. diff --git a/static-buffer/lib.rs b/static-buffer/lib.rs new file mode 100644 index 00000000..9f813f46 --- /dev/null +++ b/static-buffer/lib.rs @@ -0,0 +1,124 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod static_buffer { + use ink::prelude::{ + string::String, + vec::Vec, + }; + + #[allow(unused_imports)] + use ink::env::BUFFER_SIZE; + #[ink(storage)] + pub struct StaticBuffer { + value: bool, + } + + impl StaticBuffer { + /// Creates a dummy smart contract. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { value: init_value } + } + + /// Sets a default value. + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + /// Returns the caller of the contract. + /// Should panic if the buffer size is less than 32 bytes. + #[ink(message)] + pub fn get_caller(&self) -> AccountId { + self.env().caller() + } + + #[ink(message)] + pub fn buffer(&self) -> Result<(u64, u64), String> { + let buf1 = Vec::::with_capacity(3); + let buf2 = Vec::::with_capacity(1); + let ptr1 = buf1.as_ptr() as u64; + let ptr2 = buf2.as_ptr() as u64; + let align = core::mem::align_of::>() as u64; + let padding = ptr2 + .checked_sub(ptr1) + .ok_or(String::from("Error during padding calculation"))?; + Ok((padding, align)) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + #[should_panic(expected = "the output buffer is too small!")] + fn run_out_buffer_memory() { + let flipper = StaticBuffer::new(false); + flipper.get_caller() + } + } + + #[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_run_out_of_buffer_memory( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StaticBufferRef::new(false); + let contract = client + .instantiate("static_buffer", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // when + let get = call_builder.get_caller(); + // then panics if `INK_STATIC_BUFFER_SIZE` is less than 32 bytes. + let res = client.call(&ink_e2e::bob(), &get).dry_run().await; + println!("{}", super::BUFFER_SIZE); + assert!( + res.is_err(), + "Buffer size was larger than expected: {}", + super::BUFFER_SIZE.to_string() + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn buffer(mut client: Client) -> E2EResult<()> { + // given + let mut constructor = StaticBufferRef::new_default(); + + // when + let contract = client + .instantiate("static_buffer", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let buffer_call = call_builder.buffer(); + let buffer_call_res = + client.call(&ink_e2e::bob(), &buffer_call).submit().await?; + let value = buffer_call_res.return_value(); + assert!(value.is_ok()); + let value = value.unwrap(); + let padding = value.0; + let align = value.1; + assert_eq!(padding, 8); + assert_eq!(align, 4); + Ok(()) + } + } +} diff --git a/trait-dyn-cross-contract-calls/Cargo.toml b/trait-dyn-cross-contract-calls/Cargo.toml index 8ed80001..3202e8b7 100644 --- a/trait-dyn-cross-contract-calls/Cargo.toml +++ b/trait-dyn-cross-contract-calls/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "trait-incrementer-caller" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } dyn-traits = { path = "./traits", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } trait-incrementer = { path = "./contracts/incrementer", default-features = false, features = ["ink-as-dependency"] } [lib] diff --git a/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml b/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml index f2bfa0d3..963a0bd0 100644 --- a/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml +++ b/trait-dyn-cross-contract-calls/contracts/incrementer/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "trait-incrementer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } dyn-traits = { path = "../../traits", default-features = false } diff --git a/trait-dyn-cross-contract-calls/traits/Cargo.toml b/trait-dyn-cross-contract-calls/traits/Cargo.toml index 1e12e61d..47ca4847 100644 --- a/trait-dyn-cross-contract-calls/traits/Cargo.toml +++ b/trait-dyn-cross-contract-calls/traits/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "dyn-traits" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/trait-erc20/Cargo.toml b/trait-erc20/Cargo.toml index 3e213866..4ff939b9 100644 --- a/trait-erc20/Cargo.toml +++ b/trait-erc20/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "trait_erc20" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/trait-flipper/Cargo.toml b/trait-flipper/Cargo.toml index 580403e5..902bf50e 100644 --- a/trait-flipper/Cargo.toml +++ b/trait-flipper/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "trait_flipper" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/trait-incrementer/Cargo.toml b/trait-incrementer/Cargo.toml index 9419a759..0424e129 100644 --- a/trait-incrementer/Cargo.toml +++ b/trait-incrementer/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "trait-incrementer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } traits = { path = "./traits", default-features = false } diff --git a/trait-incrementer/traits/Cargo.toml b/trait-incrementer/traits/Cargo.toml index de19fe45..a1bd0255 100644 --- a/trait-incrementer/traits/Cargo.toml +++ b/trait-incrementer/traits/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "traits" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] name = "traits" diff --git a/upgradeable-contracts/.gitignore b/upgradeable-contracts/.gitignore new file mode 100644 index 00000000..ff75a1d9 --- /dev/null +++ b/upgradeable-contracts/.gitignore @@ -0,0 +1,2 @@ +**/target/ +Cargo.lock \ No newline at end of file diff --git a/upgradeable-contracts/README.md b/upgradeable-contracts/README.md index 74427147..8b831082 100644 --- a/upgradeable-contracts/README.md +++ b/upgradeable-contracts/README.md @@ -29,7 +29,7 @@ called again, since it will fail to load the migrated storage. ## [Delegator](delegator/) -Delegator pattern is based around a low level cross contract call function `delegate_call`. +The Delegator pattern is based around the low level host 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 @@ -49,3 +49,14 @@ This is because `Lazy` and `Mapping` interact with the storage directly instead 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. +### Delegate dependency locks + +The `delegator` contract depends upon the contract code to which it delegates. Since code +can be deleted by anybody if there are no instances of the contract on the chain, this +would break the `delegator` contract. To prevent this, the `delegator` contract utilizes +the `lock_delegate_dependency` and `unlock_delegate_dependency` host functions. Calling +`lock_delegate_dependency` will prevent the code at the given hash from being deleted, +until `unlock_delegate_dependency` is called from within the `delegator` contract instance. +Note that these two methods can be called by anybody executing the contract, so it is the +responsibility of the contract developer to ensure correct access control. + diff --git a/upgradeable-contracts/delegator/Cargo.toml b/upgradeable-contracts/delegator/Cargo.toml index fd65331d..95fe9f30 100644 --- a/upgradeable-contracts/delegator/Cargo.toml +++ b/upgradeable-contracts/delegator/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "delegator" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } delegatee = { path = "delegatee", default-features = false, features = ["ink-as-dependency"] } +delegatee2 = { path = "delegatee2", default-features = false, features = ["ink-as-dependency"] } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } [lib] path = "lib.rs" diff --git a/upgradeable-contracts/delegator/delegatee/Cargo.toml b/upgradeable-contracts/delegator/delegatee/Cargo.toml index 844c9d2e..3db5e598 100644 --- a/upgradeable-contracts/delegator/delegatee/Cargo.toml +++ b/upgradeable-contracts/delegator/delegatee/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "delegatee" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/upgradeable-contracts/delegator/delegatee2/Cargo.toml b/upgradeable-contracts/delegator/delegatee2/Cargo.toml new file mode 100644 index 00000000..feffe279 --- /dev/null +++ b/upgradeable-contracts/delegator/delegatee2/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "delegatee2" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/upgradeable-contracts/delegator/delegatee2/lib.rs b/upgradeable-contracts/delegator/delegatee2/lib.rs new file mode 100644 index 00000000..447db46e --- /dev/null +++ b/upgradeable-contracts/delegator/delegatee2/lib.rs @@ -0,0 +1,37 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +pub mod delegatee2 { + use ink::storage::{ + traits::ManualKey, + Mapping, + }; + #[ink(storage)] + pub struct Delegatee2 { + addresses: Mapping>, + counter: i32, + } + + impl Delegatee2 { + #[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(3).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/upgradeable-contracts/delegator/lib.rs b/upgradeable-contracts/delegator/lib.rs index 10c26dc6..75b43bb0 100644 --- a/upgradeable-contracts/delegator/lib.rs +++ b/upgradeable-contracts/delegator/lib.rs @@ -2,11 +2,6 @@ #[ink::contract] pub mod delegator { - use ink::{ - env::CallFlags, - storage::Mapping, - }; - use ink::{ env::{ call::{ @@ -14,40 +9,67 @@ pub mod delegator { ExecutionInput, Selector, }, + CallFlags, DefaultEnvironment, }, - storage::traits::ManualKey, + storage::{ + traits::ManualKey, + Lazy, + Mapping, + }, }; #[ink(storage)] pub struct Delegator { addresses: Mapping>, counter: i32, + delegate_to: Lazy, } impl Delegator { - /// Creates a new delegator smart contract initialized with the given value. + /// Creates a new delegator smart contract with an initial value, and the hash of + /// the contract code to delegate to. + /// + /// Additionally, this code hash will be locked to prevent its deletion, since + /// this contract depends on it. #[ink(constructor)] - pub fn new(init_value: i32) -> Self { + pub fn new(init_value: i32, hash: Hash) -> Self { let v = Mapping::new(); + + // Initialize the hash of the contract to delegate to. + // Adds a delegate dependency lock, ensuring that the delegated to code cannot + // be removed. + let mut delegate_to = Lazy::new(); + delegate_to.set(&hash); + Self::env().lock_delegate_dependency(&hash); + Self { addresses: v, counter: init_value, + delegate_to, } } - /// Creates a new contract with default values. - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) + /// Update the hash of the contract to delegate to. + /// - Unlocks the old delegate dependency, releasing the deposit and allowing old + /// delegated to code to be removed. + /// - Adds a new delegate dependency lock, ensuring that the new delegated to code + /// cannot be removed. + #[ink(message)] + pub fn update_delegate_to(&mut self, hash: Hash) { + if let Some(old_hash) = self.delegate_to.get() { + self.env().unlock_delegate_dependency(&old_hash) + } + self.env().lock_delegate_dependency(&hash); + self.delegate_to.set(&hash); } /// Increment the current value using delegate call. #[ink(message)] - pub fn inc_delegate(&mut self, hash: Hash) { + pub fn inc_delegate(&mut self) { let selector = ink::selector_bytes!("inc"); let _ = build_call::() - .delegate(hash) + .delegate(self.delegate_to()) // 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, @@ -66,10 +88,10 @@ pub mod delegator { /// 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) { + pub fn add_entry_delegate(&mut self) { let selector = ink::selector_bytes!("append_address_value"); let _ = build_call::() - .delegate(hash) + .delegate(self.delegate_to()) .exec_input(ExecutionInput::new(Selector::new(selector))) .returns::<()>() .try_invoke(); @@ -86,6 +108,12 @@ pub mod delegator { pub fn get_value(&self, address: AccountId) -> (AccountId, Option) { (self.env().caller(), self.addresses.get(address)) } + + fn delegate_to(&self) -> Hash { + self.delegate_to + .get() + .expect("delegate_to always has a value") + } } #[cfg(all(test, feature = "e2e-tests"))] @@ -107,14 +135,6 @@ pub mod delegator { .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) .await; - let mut constructor = DelegatorRef::new_default(); - let contract = client - .instantiate("delegator", &origin, &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder = contract.call_builder::(); - let code_hash = client .upload("delegatee", &origin) .submit() @@ -122,8 +142,16 @@ pub mod delegator { .expect("upload `delegatee` failed") .code_hash; + let mut constructor = DelegatorRef::new(0, code_hash); + let contract = client + .instantiate("delegator", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + // when - let call_delegate = call_builder.inc_delegate(code_hash); + let call_delegate = call_builder.inc_delegate(); let result = client.call(&origin, &call_delegate).submit().await; assert!(result.is_ok(), "delegate call failed."); @@ -159,8 +187,15 @@ pub mod delegator { .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) .await; + let code_hash = client + .upload("delegatee", &origin) + .submit() + .await + .expect("upload `delegatee` failed") + .code_hash; + // given - let mut constructor = DelegatorRef::new(10); + let mut constructor = DelegatorRef::new(10, code_hash); let contract = client .instantiate("delegator", &origin, &mut constructor) .submit() @@ -168,15 +203,8 @@ pub mod delegator { .expect("instantiate failed"); let mut call_builder = contract.call_builder::(); - let code_hash = client - .upload("delegatee", &origin) - .submit() - .await - .expect("upload `delegatee` failed") - .code_hash; - // when - let call_delegate = call_builder.add_entry_delegate(code_hash); + let call_delegate = call_builder.add_entry_delegate(); let result = client.call(&origin, &call_delegate).submit().await; assert!(result.is_ok(), "delegate call failed."); @@ -204,5 +232,57 @@ pub mod delegator { Ok(()) } + + #[ink_e2e::test] + async fn update_delegate( + mut client: Client, + ) -> E2EResult<()> { + // given + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let code_hash = client + .upload("delegatee", &origin) + .submit() + .await + .expect("upload `delegatee` failed") + .code_hash; + + let code_hash2 = client + .upload("delegatee2", &origin) + .submit() + .await + .expect("upload `delegatee2` failed") + .code_hash; + + let mut constructor = DelegatorRef::new(10, code_hash); + let contract = client + .instantiate("delegator", &origin, &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + + // when + let call_delegate = call_builder.update_delegate_to(code_hash2); + let result = client.call(&origin, &call_delegate).submit().await; + assert!(result.is_ok(), "update_delegate_to failed."); + + // then + + // remove the original delegatee code. + // should succeed because the delegate dependency has been removed. + let original_code_removed = + client.remove_code(&origin, code_hash).submit().await; + assert!(original_code_removed.is_ok()); + + // attempt to remove the new delegatee code. + // should fail because of the delegate dependency. + let new_code_removed = client.remove_code(&origin, code_hash2).submit().await; + assert!(new_code_removed.is_err()); + + Ok(()) + } } } diff --git a/upgradeable-contracts/set-code-hash-migration/.gitignore b/upgradeable-contracts/set-code-hash-migration/.gitignore new file mode 100644 index 00000000..bf910de1 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/.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/upgradeable-contracts/set-code-hash-migration/Cargo.toml b/upgradeable-contracts/set-code-hash-migration/Cargo.toml new file mode 100644 index 00000000..2ba291b7 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "incrementer" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +migration = { path = "./migration", default-features = false, features = ["ink-as-dependency"] } +updated-incrementer = { path = "./updated-incrementer", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "migration/std", + "updated-incrementer/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/upgradeable-contracts/set-code-hash-migration/e2e_tests.rs b/upgradeable-contracts/set-code-hash-migration/e2e_tests.rs new file mode 100644 index 00000000..dbbd029a --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/e2e_tests.rs @@ -0,0 +1,94 @@ +use super::incrementer::*; +use ink_e2e::ContractsBackend; + +type E2EResult = std::result::Result>; + +#[ink_e2e::test] +async fn migration_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_builder = contract.call_builder::(); + + let get = call_builder.get(); + let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; + assert_eq!(get_res.return_value(), 0); + + let inc = call_builder.inc(); + let _inc_result = client + .call(&ink_e2e::alice(), &inc) + .submit() + .await + .expect("`inc` failed"); + + let get = call_builder.get(); + let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; + let pre_migration_value = get_res.return_value(); + assert_eq!(pre_migration_value, 1); + + // Upload the code for the contract to be updated to after the migration. + 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(); + + // Upload the code for the migration contract. + let migration_contract = client + .upload("migration", &ink_e2e::alice()) + .submit() + .await + .expect("uploading `migration` failed"); + let migration_code_hash = migration_contract.code_hash.as_ref().try_into().unwrap(); + + // When + + // Set the code hash to the migration contract + let set_code = call_builder.set_code(migration_code_hash); + let _set_code_result = client + .call(&ink_e2e::alice(), &set_code) + .submit() + .await + .expect("`set_code` failed"); + + // Call the migration contract with a new value for `inc_by` and the code hash + // of the updated contract. + const NEW_INC_BY: u8 = 4; + let migrate = contract + .call_builder::() + .migrate(NEW_INC_BY, new_code_hash); + + let _migration_result = client + .call(&ink_e2e::alice(), &migrate) + .submit() + .await + .expect("`migrate` failed"); + + // Then + let inc = contract + .call_builder::() + .inc(); + + let _inc_result = client + .call(&ink_e2e::alice(), &inc) + .submit() + .await + .expect("`inc` failed"); + + let get = call_builder.get(); + let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await?; + + // Remember, we updated our incrementer contract to increment by `4`. + assert_eq!( + get_res.return_value(), + pre_migration_value + NEW_INC_BY as u32 + ); + + Ok(()) +} diff --git a/upgradeable-contracts/set-code-hash-migration/lib.rs b/upgradeable-contracts/set-code-hash-migration/lib.rs new file mode 100644 index 00000000..ea67cf19 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/lib.rs @@ -0,0 +1,69 @@ +#![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 `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; diff --git a/upgradeable-contracts/set-code-hash-migration/migration/Cargo.toml b/upgradeable-contracts/set-code-hash-migration/migration/Cargo.toml new file mode 100644 index 00000000..07841671 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/migration/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "migration" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/upgradeable-contracts/set-code-hash-migration/migration/lib.rs b/upgradeable-contracts/set-code-hash-migration/migration/lib.rs new file mode 100644 index 00000000..93d13cc6 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/migration/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 { + + /// Storage struct matches exactly that of the original `incrementer` contract, from + /// which we are migrating. + #[ink(storage)] + pub struct Incrementer { + count: u32, + } + + #[ink::storage_item] + pub struct IncrementerNew { + count: u64, + inc_by: u8, + } + + 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`." + ) + } + + /// Run the migration to the data layout for the upgraded contract. + /// Once the storage migration has successfully completed, the contract will be + /// upgraded to the supplied code hash. + /// + /// In a production contract you would do some authorization here! + /// + /// # Note + /// + /// This function necessarily accepts a `&self` instead of a `&mut self` because + /// we are modifying storage directly for the migration. + /// + /// The `self` in `&mut self` is the original `Incrementer` storage struct, and + /// would be implicitly written to storage following the function execution, + /// overwriting the migrated storage. + #[ink(message)] + pub fn migrate(&self, inc_by: u8, code_hash: Hash) { + let incrementer_new = IncrementerNew { + count: self.count as u64, + inc_by, + }; + + // overwrite the original storage struct with the migrated storage struct, + // which has a layout compatible with the new contract code. + const STORAGE_KEY: u32 = + ::KEY; + ink::env::set_contract_storage(&STORAGE_KEY, &incrementer_new); + + ink::env::set_code_hash::<::Env>(&code_hash) + .unwrap_or_else(|err| { + panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") + }) + } + } +} diff --git a/upgradeable-contracts/set-code-hash-migration/updated-incrementer/Cargo.toml b/upgradeable-contracts/set-code-hash-migration/updated-incrementer/Cargo.toml new file mode 100644 index 00000000..a541ecb9 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/updated-incrementer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "updated-incrementer" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] diff --git a/upgradeable-contracts/set-code-hash-migration/updated-incrementer/lib.rs b/upgradeable-contracts/set-code-hash-migration/updated-incrementer/lib.rs new file mode 100644 index 00000000..7ac05c80 --- /dev/null +++ b/upgradeable-contracts/set-code-hash-migration/updated-incrementer/lib.rs @@ -0,0 +1,75 @@ +#![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 changed the storage layout: + /// - `count` is now a `u64` instead of a `u32`. + /// - We have added a new field `inc_by` which controls how many to increment by. + #[ink(storage)] + pub struct Incrementer { + count: u64, + inc_by: u8, + } + + 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 + /// + /// In this upgraded contract the value is incremented by the value in the + /// `inc_by` field. + #[ink(message)] + pub fn inc(&mut self) { + self.count = self.count.checked_add(self.inc_by.into()).unwrap(); + } + + /// Set the value by which the counter will be incremented. + #[ink(message)] + pub fn set_inc_by(&mut self, inc_by: u8) { + self.inc_by = inc_by; + } + + /// Returns the counter value which is stored in this contract's storage. + #[ink(message)] + pub fn get(&self) -> u64 { + 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/upgradeable-contracts/set-code-hash/.gitignore b/upgradeable-contracts/set-code-hash/.gitignore new file mode 100644 index 00000000..bf910de1 --- /dev/null +++ b/upgradeable-contracts/set-code-hash/.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/upgradeable-contracts/set-code-hash/Cargo.toml b/upgradeable-contracts/set-code-hash/Cargo.toml index 97cd7a7d..c2a6cf89 100644 --- a/upgradeable-contracts/set-code-hash/Cargo.toml +++ b/upgradeable-contracts/set-code-hash/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "incrementer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] -ink_e2e = { version = "5.0.0-rc.1" } +ink_e2e = { version = "5.0.0-rc.2" } updated-incrementer = { path = "updated-incrementer", default-features = false, features = ["ink-as-dependency"] } [lib] diff --git a/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml b/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml index 80b273ee..a541ecb9 100644 --- a/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml +++ b/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "updated-incrementer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [lib] path = "lib.rs" diff --git a/vesting/Cargo.toml b/vesting/Cargo.toml index 602b4da7..c522e392 100755 --- a/vesting/Cargo.toml +++ b/vesting/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "vesting" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" [dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } +ink = { version = "5.0.0-rc.2", default-features = false } [dev-dependencies] diff --git a/wildcard-selector/.gitignore b/wildcard-selector/.gitignore new file mode 100644 index 00000000..bf910de1 --- /dev/null +++ b/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/wildcard-selector/Cargo.toml b/wildcard-selector/Cargo.toml new file mode 100644 index 00000000..271d53e0 --- /dev/null +++ b/wildcard-selector/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "wildcard-selector" +version = "5.0.0-rc.2" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { version = "5.0.0-rc.2", default-features = false } + +[dev-dependencies] +ink_e2e = { version = "5.0.0-rc.2" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/wildcard-selector/lib.rs b/wildcard-selector/lib.rs new file mode 100644 index 00000000..1e99545b --- /dev/null +++ b/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(()) + } + } +} diff --git a/workspace-contracts/Cargo.toml b/workspace-contracts/Cargo.toml index 28488b6d..f9e6c032 100644 --- a/workspace-contracts/Cargo.toml +++ b/workspace-contracts/Cargo.toml @@ -4,5 +4,5 @@ exclude = [".cargo", "target"] resolver = "2" [workspace.dependencies] -ink = { version = "5.0.0-rc.1", default-features = false } -ink_e2e = "5.0.0-rc.1" +ink = { version = "5.0.0-rc.2", default-features = false } +ink_e2e = "5.0.0-rc.2" diff --git a/workspace-contracts/flipper/Cargo.toml b/workspace-contracts/flipper/Cargo.toml index dc5be94d..fc97e94f 100644 --- a/workspace-contracts/flipper/Cargo.toml +++ b/workspace-contracts/flipper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flipper" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false diff --git a/workspace-contracts/incrementer/Cargo.toml b/workspace-contracts/incrementer/Cargo.toml index 41a479d4..c41ed021 100644 --- a/workspace-contracts/incrementer/Cargo.toml +++ b/workspace-contracts/incrementer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "incrementer" -version = "5.0.0-rc.1" +version = "5.0.0-rc.2" authors = ["Parity Technologies "] edition = "2021" publish = false