From 16e45d34fe2ef84b2a7c1cd6cd48143007cbe08c Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 3 Mar 2023 04:25:28 -0800 Subject: [PATCH] Clean up upgradeable contract examples (#1697) * Move `set-code-hash` example out of `upgradeable-contracts` folder * Clean up code a bit * Rename folder to use snake_case * More snake casing * Make it more explicit that upgraded constructor won't be called * Add an E2E test showing upgrade workflow * Remove `forward-calls` example This wasn't really a good showcase of an upgradeable contract. This is because it didn't use `delegate_call`, meaning that the `Proxy` contract wouldn't be able to retain state between updates. * Remove `upgradeable-contracts` from CI * Move `edition` after `author` field * Use American spelling, I guess... * Fix some paths * More path fixes * Remove comment * Move `edition` below `authors` key * Appease Clippy * Ensure that initial `inc` is only by `1` --- .gitlab-ci.yml | 37 +---- .../Cargo.toml | 14 +- integration-tests/set_code_hash/lib.rs | 138 ++++++++++++++++++ .../updated_incrementer}/Cargo.toml | 4 +- .../updated_incrementer}/lib.rs | 39 +++-- .../upgradeable-contracts/README.md | 29 ---- .../forward-calls/.gitignore | 9 -- .../forward-calls/README.md | 40 ----- .../forward-calls/lib.rs | 98 ------------- .../set-code-hash/lib.rs | 54 ------- .../updated-incrementer/Cargo.toml | 28 ---- 11 files changed, 175 insertions(+), 315 deletions(-) rename integration-tests/{upgradeable-contracts/set-code-hash => set_code_hash}/Cargo.toml (70%) create mode 100644 integration-tests/set_code_hash/lib.rs rename integration-tests/{upgradeable-contracts/forward-calls => set_code_hash/updated_incrementer}/Cargo.toml (88%) rename integration-tests/{upgradeable-contracts/set-code-hash/updated-incrementer => set_code_hash/updated_incrementer}/lib.rs (57%) delete mode 100644 integration-tests/upgradeable-contracts/README.md delete mode 100644 integration-tests/upgradeable-contracts/forward-calls/.gitignore delete mode 100644 integration-tests/upgradeable-contracts/forward-calls/README.md delete mode 100644 integration-tests/upgradeable-contracts/forward-calls/lib.rs delete mode 100644 integration-tests/upgradeable-contracts/set-code-hash/lib.rs delete mode 100644 integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c243d4a810f..cd95f9883e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,7 +33,6 @@ variables: ALSO_WASM_CRATES: "env storage storage/traits allocator prelude primitives ink ink/macro ink/ir" ALL_CRATES: "${PURELY_STD_CRATES} ${ALSO_WASM_CRATES}" DELEGATOR_SUBCONTRACTS: "accumulator adder subber" - UPGRADEABLE_CONTRACTS: "forward-calls set-code-hash" LANG_ERR_INTEGRATION_CONTRACTS: "integration-flipper call-builder contract-ref constructors-return-value" # TODO `cargo clippy --verbose --all-targets --all-features` for this crate # currently fails on `stable`, but succeeds on `nightly`. This is due to @@ -116,20 +115,16 @@ examples-fmt: script: # Note that we disable the license header check for the examples, since they are unlicensed. - for example in integration-tests/*/; do - if [ "$example" = "integration-tests/upgradeable-contracts/" ]; then continue; fi; if [ "$example" = "integration-tests/lang-err-integration-tests/" ]; then continue; fi; cargo +nightly fmt --verbose --manifest-path ${example}/Cargo.toml -- --check; done - for contract in ${DELEGATOR_SUBCONTRACTS}; do cargo +nightly fmt --verbose --manifest-path ./integration-tests/delegator/${contract}/Cargo.toml -- --check; done - - for contract in ${UPGRADEABLE_CONTRACTS}; do - cargo +nightly fmt --verbose --manifest-path ./integration-tests/upgradeable-contracts/${contract}/Cargo.toml -- --check; - done - for contract in ${LANG_ERR_INTEGRATION_CONTRACTS}; do cargo +nightly fmt --verbose --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml -- --check; done - - cargo +nightly fmt --verbose --manifest-path ./integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml -- --check + - cargo +nightly fmt --verbose --manifest-path ./integration-tests/set_code_hash/updated_incrementer/Cargo.toml -- --check # This file is not a part of the cargo project, so it wouldn't be formatted the usual way - rustfmt +nightly --verbose --check ./integration-tests/psp22-extension/runtime/psp22-extension-example.rs allow_failure: true @@ -159,20 +154,16 @@ examples-clippy-std: <<: *test-refs script: - for example in integration-tests/*/; do - if [ "$example" = "integration-tests/upgradeable-contracts/" ]; then continue; fi; if [ "$example" = "integration-tests/lang-err-integration-tests/" ]; then continue; fi; cargo clippy --verbose --all-targets --manifest-path ${example}/Cargo.toml -- -D warnings -A $CLIPPY_ALLOWED; done - for contract in ${DELEGATOR_SUBCONTRACTS}; do cargo clippy --verbose --all-targets --manifest-path ./integration-tests/delegator/${contract}/Cargo.toml -- -D warnings -A $CLIPPY_ALLOWED; done - - for contract in ${UPGRADEABLE_CONTRACTS}; do - cargo clippy --verbose --all-targets --manifest-path ./integration-tests/upgradeable-contracts/${contract}/Cargo.toml -- -D warnings -A $CLIPPY_ALLOWED; - done - for contract in ${LANG_ERR_INTEGRATION_CONTRACTS}; do cargo clippy --verbose --all-targets --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml -- -D warnings -A $CLIPPY_ALLOWED; done - - cargo clippy --verbose --all-targets --manifest-path ./integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml -- -D warnings -A $CLIPPY_ALLOWED; + - cargo clippy --verbose --all-targets --manifest-path ./integration-tests/set_code_hash/updated_incrementer/Cargo.toml -- -D warnings -A $CLIPPY_ALLOWED; allow_failure: true examples-clippy-wasm: @@ -181,20 +172,16 @@ examples-clippy-wasm: <<: *test-refs script: - for example in integration-tests/*/; do - if [ "$example" = "integration-tests/upgradeable-contracts/" ]; then continue; fi; if [ "$example" = "integration-tests/lang-err-integration-tests/" ]; then continue; fi; cargo clippy --verbose --manifest-path ${example}/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings -A $CLIPPY_ALLOWED; done - for contract in ${DELEGATOR_SUBCONTRACTS}; do cargo clippy --verbose --manifest-path ./integration-tests/delegator/${contract}/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings -A $CLIPPY_ALLOWED; done - - for contract in ${UPGRADEABLE_CONTRACTS}; do - cargo clippy --verbose --manifest-path ./integration-tests/upgradeable-contracts/${contract}/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings -A $CLIPPY_ALLOWED; - done - for contract in ${LANG_ERR_INTEGRATION_CONTRACTS}; do cargo clippy --verbose --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings -A $CLIPPY_ALLOWED; done - - cargo clippy --verbose --manifest-path ./integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings -A $CLIPPY_ALLOWED; + - cargo clippy --verbose --manifest-path ./integration-tests/set_code_hash/updated_incrementer/Cargo.toml --no-default-features --target wasm32-unknown-unknown -- -D warnings -A $CLIPPY_ALLOWED; allow_failure: true @@ -370,7 +357,6 @@ examples-test: artifacts: false script: - for example in integration-tests/*/; do - if [ "$example" = "integration-tests/upgradeable-contracts/" ]; then continue; fi; if [ "$example" = "integration-tests/lang-err-integration-tests/" ]; then continue; fi; if grep -q "e2e-tests = \[\]" "${example}/Cargo.toml"; then cargo test --verbose --manifest-path ${example}/Cargo.toml --features e2e-tests; @@ -381,16 +367,13 @@ examples-test: - for contract in ${DELEGATOR_SUBCONTRACTS}; do cargo test --verbose --manifest-path ./integration-tests/delegator/${contract}/Cargo.toml; done - - for contract in ${UPGRADEABLE_CONTRACTS}; do - cargo test --verbose --manifest-path ./integration-tests/upgradeable-contracts/${contract}/Cargo.toml; - done # TODO (#1502): We need to clean before running, otherwise the CI fails with a # linking error. - for contract in ${LANG_ERR_INTEGRATION_CONTRACTS}; do cargo clean --verbose --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml; cargo test --verbose --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml --features e2e-tests; done - - cargo test --verbose --manifest-path ./integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml; + - cargo test --verbose --manifest-path ./integration-tests/set_code_hash/updated_incrementer/Cargo.toml; examples-contract-build: stage: examples @@ -400,20 +383,16 @@ examples-contract-build: - rustup component add rust-src --toolchain stable - cargo contract -V - for example in integration-tests/*/; do - if [ "$example" = "integration-tests/upgradeable-contracts/" ]; then continue; fi; if [ "$example" = "integration-tests/lang-err-integration-tests/" ]; then continue; fi; pushd $example && cargo +stable contract build && popd; done - pushd ./integration-tests/delegator/ && ./build-all.sh && popd - - for contract in ${UPGRADEABLE_CONTRACTS}; do - cargo +stable contract build --manifest-path ./integration-tests/upgradeable-contracts/${contract}/Cargo.toml; - done - for contract in ${LANG_ERR_INTEGRATION_CONTRACTS}; do cargo +stable contract build --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml; done - - cargo +stable contract build --manifest-path ./integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml + - cargo +stable contract build --manifest-path ./integration-tests/set_code_hash/updated_incrementer/Cargo.toml examples-docs: stage: examples @@ -427,20 +406,16 @@ examples-docs: # Once https://github.com/paritytech/ink/issues/336 has been implemented we can get rid # of this flag. - for example in integration-tests/*/; do - if [ "$example" = "integration-tests/upgradeable-contracts/" ]; then continue; fi; if [ "$example" = "integration-tests/lang-err-integration-tests/" ]; then continue; fi; cargo doc --manifest-path ${example}/Cargo.toml --document-private-items --verbose --no-deps; done - for contract in ${DELEGATOR_SUBCONTRACTS}; do cargo doc --manifest-path ./integration-tests/delegator/${contract}/Cargo.toml --document-private-items --verbose --no-deps; done - - for contract in ${UPGRADEABLE_CONTRACTS}; do - cargo doc --manifest-path ./integration-tests/upgradeable-contracts/${contract}/Cargo.toml --document-private-items --verbose --no-deps; - done - for contract in ${LANG_ERR_INTEGRATION_CONTRACTS}; do cargo doc --manifest-path ./integration-tests/lang-err-integration-tests/${contract}/Cargo.toml --document-private-items --verbose --no-deps; done - - cargo doc --manifest-path ./integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml --document-private-items --verbose --no-deps + - cargo doc --manifest-path ./integration-tests/set_code_hash/updated_incrementer/Cargo.toml --document-private-items --verbose --no-deps #### stage: ink-waterfall diff --git a/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml b/integration-tests/set_code_hash/Cargo.toml similarity index 70% rename from integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml rename to integration-tests/set_code_hash/Cargo.toml index 27b102b19b7..6a4a69b4dda 100644 --- a/integration-tests/upgradeable-contracts/set-code-hash/Cargo.toml +++ b/integration-tests/set_code_hash/Cargo.toml @@ -1,22 +1,21 @@ [package] name = "incrementer" version = "4.0.1" -edition = "2021" authors = ["Parity Technologies "] +edition = "2021" publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -ink = { path = "../../../crates/ink", default-features = false } +ink = { path = "../../crates/ink", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + [lib] -name = "incrementer" path = "lib.rs" -crate-type = ["cdylib"] [features] default = ["std"] @@ -26,5 +25,4 @@ std = [ "scale-info/std", ] ink-as-dependency = [] - - +e2e-tests = [] diff --git a/integration-tests/set_code_hash/lib.rs b/integration-tests/set_code_hash/lib.rs new file mode 100644 index 00000000000..dfade0ed9f0 --- /dev/null +++ b/integration-tests/set_code_hash/lib.rs @@ -0,0 +1,138 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! Demonstrates how to use [`set_code_hash`](https://docs.rs/ink_env/latest/ink_env/fn.set_code_hash.html) +//! to swap out the `code_hash` of an on-chain contract. +//! +//! We will swap the code of our `Incrementer` contract with that of the an `Incrementer` found in +//! the `updated_incrementer` folder. +//! +//! See the included End-to-End tests an example update workflow. + +#[ink::contract] +pub mod incrementer { + + /// Track a counter in storage. + /// + /// # Note + /// + /// Is is important to realize that after the call to `set_code_hash` the contract's storage + /// remains the same. + /// + /// If you change the storage layout in your storage struct you may introduce undefined + /// behavior to your contract! + #[ink(storage)] + #[derive(Default)] + pub struct Incrementer { + count: u32, + } + + impl Incrementer { + /// Creates a new counter smart contract initialized with the given base value. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + /// Increments the counter value which is stored in the contract's storage. + #[ink(message)] + pub fn inc(&mut self) { + self.count += 1; + 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: [u8; 32]) { + ink::env::set_code_hash(&code_hash).unwrap_or_else(|err| { + panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") + }); + ink::env::debug_println!("Switched code hash to {:?}.", code_hash); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::build_message; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test(additional_contracts = "./updated_incrementer/Cargo.toml")] + async fn set_code_works(mut client: ink_e2e::Client) -> E2EResult<()> { + // Given + let constructor = IncrementerRef::new(); + let contract_acc_id = client + .instantiate("incrementer", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let get = build_message::(contract_acc_id.clone()) + .call(|incrementer| incrementer.get()); + let get_res = client.call_dry_run(&ink_e2e::alice(), &get, 0, None).await; + assert!(matches!(get_res.return_value(), 0)); + + let inc = build_message::(contract_acc_id.clone()) + .call(|incrementer| incrementer.inc()); + let _inc_result = client + .call(&ink_e2e::alice(), inc, 0, None) + .await + .expect("`inc` failed"); + + let get = build_message::(contract_acc_id.clone()) + .call(|incrementer| incrementer.get()); + let get_res = client.call_dry_run(&ink_e2e::alice(), &get, 0, None).await; + assert!(matches!(get_res.return_value(), 1)); + + // When + let new_code_hash = client + .upload("updated_incrementer", &ink_e2e::alice(), None) + .await + .expect("uploading `updated_incrementer` failed") + .code_hash; + + let new_code_hash = new_code_hash.as_ref().try_into().unwrap(); + let set_code = build_message::(contract_acc_id.clone()) + .call(|incrementer| incrementer.set_code(new_code_hash)); + + let _set_code_result = client + .call(&ink_e2e::alice(), set_code, 0, None) + .await + .expect("`set_code` failed"); + + // Then + // Note that our contract's `AccountId` (so `contract_acc_id`) has stayed the same + // between updates! + let inc = build_message::(contract_acc_id.clone()) + .call(|incrementer| incrementer.inc()); + + let _inc_result = client + .call(&ink_e2e::alice(), inc, 0, None) + .await + .expect("`inc` failed"); + + let get = build_message::(contract_acc_id.clone()) + .call(|incrementer| incrementer.get()); + let get_res = client.call_dry_run(&ink_e2e::alice(), &get, 0, None).await; + + // Remember, we updated our incrementer contract to increment by `4`. + assert!(matches!(get_res.return_value(), 5)); + + Ok(()) + } + } +} diff --git a/integration-tests/upgradeable-contracts/forward-calls/Cargo.toml b/integration-tests/set_code_hash/updated_incrementer/Cargo.toml similarity index 88% rename from integration-tests/upgradeable-contracts/forward-calls/Cargo.toml rename to integration-tests/set_code_hash/updated_incrementer/Cargo.toml index 4173183631d..23fc170ea38 100644 --- a/integration-tests/upgradeable-contracts/forward-calls/Cargo.toml +++ b/integration-tests/set_code_hash/updated_incrementer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "forward_calls" +name = "updated_incrementer" version = "4.0.1" authors = ["Parity Technologies "] edition = "2021" @@ -12,9 +12,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } [lib] -name = "forward_calls" path = "lib.rs" -crate-type = ["cdylib"] [features] default = ["std"] diff --git a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs b/integration-tests/set_code_hash/updated_incrementer/lib.rs similarity index 57% rename from integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs rename to integration-tests/set_code_hash/updated_incrementer/lib.rs index de2b517c98e..14d4758393c 100644 --- a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/lib.rs +++ b/integration-tests/set_code_hash/updated_incrementer/lib.rs @@ -1,11 +1,17 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::new_without_default)] #[ink::contract] pub mod incrementer { - /// This struct contains the smart contract storage. + /// Track a counter in storage. /// - /// *Note:* We use exactly the same storage struct as in the originally deployed `incrementer`. + /// # Note + /// + /// We have kept the same storage layout as in our original `incrementer` contract. + /// + /// Had we changed `count` to, for example, an `AccountId` we would end up with undefined + /// behaviour in our contract. #[ink(storage)] pub struct Incrementer { count: u32, @@ -14,23 +20,24 @@ pub mod incrementer { impl Incrementer { /// Creates a new counter smart contract initialized with the given base value. /// - /// Note that with our upgrade-workflow this constructor will never actually be called, - /// since we merely replace the code used to execute a contract that was already - /// initiated on-chain. - #[ink(constructor)] - pub fn new(init_value: u32) -> Self { - Self { count: init_value } - } - - /// Creates a new counter smart contract initialized to `0`. + /// # 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_default() -> Self { - Self::new(0) + pub fn new() -> Self { + unreachable!( + "Constructors are not called when upgrading using `set_code_hash`." + ) } /// Increments the counter value which is stored in the contract's storage. /// - /// *Note:* We use a different step size here than in the original `incrementer`. + /// # Note + /// + /// We use a different step size (4) here than in the original `incrementer`. #[ink(message)] pub fn inc(&mut self) { self.count += 4; @@ -46,7 +53,9 @@ pub mod incrementer { /// 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. + /// can execute this method. + /// + /// In a production contract you would do some authorization here! #[ink(message)] pub fn set_code(&mut self, code_hash: [u8; 32]) { ink::env::set_code_hash(&code_hash).unwrap_or_else(|err| { diff --git a/integration-tests/upgradeable-contracts/README.md b/integration-tests/upgradeable-contracts/README.md deleted file mode 100644 index 706484d19f1..00000000000 --- a/integration-tests/upgradeable-contracts/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Upgradeable Contracts - -There are three upgradeable contract examples in this folder, they differ -in key properties outlined below. - -See [here](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies) for -more information on proxy patterns. - - -## [`forward-calls`](https://github.com/paritytech/ink/tree/master/examples/upgradeable-contracts/forward-calls) - -* Forwards any call that does not match a selector of itself to another contract. -* The other contract needs to be deployed on-chain. -* State is stored in the storage of the contract to which calls are forwarded. - - -## [`set-code-hash`](https://github.com/paritytech/ink/tree/master/examples/upgradeable-contracts/set-code-hash) - -* Updates the contract code using `set_code_hash`. - This effectively replaces the code which is executed for the contract address. -* The other contract (`updated-incrementer`) needs to be deployed on-chain. -* State is stored in the storage of the originally instantiated contract (`incrementer`). - -## Storage Compatibility - -When working on the contract upgradeability, it is important to observe additional rules that are imposed on -the modifications of storage: - -Please refer to the section of [Storage Compatibility](https://paritytech.github.io/ink/ink_env/fn.set_code_hash.html) in the ink! crate documentation. diff --git a/integration-tests/upgradeable-contracts/forward-calls/.gitignore b/integration-tests/upgradeable-contracts/forward-calls/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/upgradeable-contracts/forward-calls/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/upgradeable-contracts/forward-calls/README.md b/integration-tests/upgradeable-contracts/forward-calls/README.md deleted file mode 100644 index 16e8f759c4a..00000000000 --- a/integration-tests/upgradeable-contracts/forward-calls/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Upgradeable Smart Contract - -The proxy smart contract forwards any call that does not match a -selector of itself to another, specified contract. - -The instantiator of the proxy contract on a blockchain can change -the address to which calls are forwarded. - -This allows building upgradeable contracts following the proxy pattern. -Note though that the state is still stored in the contract to which -calls are forwarded. - -In order to test it out you need to do the following: - -1. Build a contract containing some logic, e.g. our flipper example: - ``` - cargo contract build --manifest-path=examples/flipper/Cargo.toml - ``` - You will receive the respective `flipper.contract` bundle in the `examples/flipper/target/ink/` folder. -1. Build the proxy contract: - ``` - cd upgradeable-contracts/forward-calls/ - cargo contract build - ``` - You will receive the respective `forwards_calls.contract` bundle in the `target/ink/` folder. -1. Upload the `flipper.contract` to the chain. -1. Upload the `forwards_calls.contract` to the chain. During instantiation specify the just instantiated - `flipper` contract as the `forward_to` parameter. -1. Switch the metadata of the just instantiated `forwards_calls` contract to the metadata of the `flipper` - contract. In the `polkadot-js` UI this can be done this way: - 1. Click the icon left of the instantiated `forwards_calls` contract to copy the address - of it into your clipboard. - 1. Click `Add an existing contract`, insert the just copied address, upload the `flipper.contract` - for the `Contract ABI`. -1. Now you are able to run the operations provided by the `flipper` smart contract via - the `forwards_calls` contract. - -To change the address of the smart contract where calls are forwarded to you would -switch the metadata (i.e. the `Contract ABI`) back to the `forwards_calls` contract -and then invoke the `change_forward_address` message. diff --git a/integration-tests/upgradeable-contracts/forward-calls/lib.rs b/integration-tests/upgradeable-contracts/forward-calls/lib.rs deleted file mode 100644 index d65c5111680..00000000000 --- a/integration-tests/upgradeable-contracts/forward-calls/lib.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! This example demonstrates how the Proxy/Forward pattern can be -//! implemented in ink!. -//! -//! What the contract does is: -//! -//! * Any call to this contract that does not match a selector -//! of it is forwarded to a specified address. -//! * The instantiator of the contract can modify this specified -//! `forward_to` address at any point. -//! -//! Using this pattern it is possible to implement upgradeable contracts. -//! -//! Note though that the contract to which calls are forwarded still -//! contains it's own state. - -#![cfg_attr(not(feature = "std"), no_std)] - -#[ink::contract] -pub mod proxy { - - /// A simple proxy contract. - #[ink(storage)] - pub struct Proxy { - /// The `AccountId` of a contract where any call that does not match a - /// selector of this contract is forwarded to. - forward_to: AccountId, - /// The `AccountId` of a privileged account that can update the - /// forwarding address. This address is set to the account that - /// instantiated this contract. - admin: AccountId, - } - - impl Proxy { - /// Instantiate this contract with an address of the `logic` contract. - /// - /// Sets the privileged account to the caller. Only this account may - /// later changed the `forward_to` address. - #[ink(constructor)] - pub fn new(forward_to: AccountId) -> Self { - Self { - admin: Self::env().caller(), - forward_to, - } - } - - /// Changes the `AccountId` of the contract where any call that does - /// not match a selector of this contract is forwarded to. - #[ink(message)] - pub fn change_forward_address(&mut self, new_address: AccountId) { - assert_eq!( - self.env().caller(), - self.admin, - "caller {:?} does not have sufficient permissions, only {:?} does", - self.env().caller(), - self.admin, - ); - self.forward_to = new_address; - } - - /// Fallback message for a contract call that doesn't match any - /// of the other message selectors. - /// - /// # Note: - /// - /// - We allow payable messages here and would forward any optionally supplied - /// value as well. - /// - If the self receiver were `forward(&mut self)` here, this would not - /// have any effect whatsoever on the contract we forward to. - #[ink(message, payable, selector = _)] - pub fn forward(&self) -> u32 { - ink::env::call::build_call::() - .call(self.forward_to) - .transferred_value(self.env().transferred_value()) - .gas_limit(0) - .call_flags( - ink::env::CallFlags::default() - .set_forward_input(true) - .set_tail_call(true), - ) - .try_invoke() - .unwrap_or_else(|env_err| { - panic!( - "cross-contract call to {:?} failed due to {:?}", - self.forward_to, env_err - ) - }) - .unwrap_or_else(|lang_err| { - panic!( - "cross-contract call to {:?} failed due to {:?}", - self.forward_to, lang_err - ) - }); - unreachable!( - "the forwarded call will never return since `tail_call` was set" - ); - } - } -} diff --git a/integration-tests/upgradeable-contracts/set-code-hash/lib.rs b/integration-tests/upgradeable-contracts/set-code-hash/lib.rs deleted file mode 100644 index ce36570a740..00000000000 --- a/integration-tests/upgradeable-contracts/set-code-hash/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -#[ink::contract] -pub mod incrementer { - - /// This struct contains the smart contract storage. - /// The storage will always be retained, even when `set_code_hash` is called. - #[ink(storage)] - pub struct Incrementer { - count: u32, - } - - impl Incrementer { - /// Creates a new counter smart contract initialized with the given base value. - #[ink(constructor)] - pub fn new(init_value: u32) -> Self { - Self { count: init_value } - } - - /// Creates a new counter smart contract initialized to `0`. - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(0) - } - - /// Increments the counter value which is stored in the contract's storage. - #[ink(message)] - pub fn inc(&mut self) { - self.count += 1; - 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: [u8; 32]) { - ink::env::set_code_hash(&code_hash).unwrap_or_else(|err| { - panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") - }); - ink::env::debug_println!("Switched code hash to {:?}.", code_hash); - } - } -} diff --git a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml b/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml deleted file mode 100644 index 0667cb79c30..00000000000 --- a/integration-tests/upgradeable-contracts/set-code-hash/updated-incrementer/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "updated-incrementer" -version = "4.0.1" -edition = "2021" -authors = ["Parity Technologies "] -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -ink = { path = "../../../../crates/ink", default-features = false } - -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } -scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } - -[lib] -name = "updated_incrementer" -path = "lib.rs" -crate-type = ["cdylib"] - -[features] -default = ["std"] -std = [ - "ink/std", - "scale/std", - "scale-info/std", -] -ink-as-dependency = []