Skip to content

Commit

Permalink
Clean up upgradeable contract examples (#1697)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
HCastano authored and ascjones committed Mar 3, 2023
1 parent fe0aad6 commit 16e45d3
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 315 deletions.
37 changes: 6 additions & 31 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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


Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
[package]
name = "incrementer"
version = "4.0.1"
edition = "2021"
authors = ["Parity Technologies <admin@parity.io>"]
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"]
Expand All @@ -26,5 +25,4 @@ std = [
"scale-info/std",
]
ink-as-dependency = []


e2e-tests = []
138 changes: 138 additions & 0 deletions integration-tests/set_code_hash/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[ink_e2e::test(additional_contracts = "./updated_incrementer/Cargo.toml")]
async fn set_code_works(mut client: ink_e2e::Client<C, E>) -> 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::<IncrementerRef>(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::<IncrementerRef>(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::<IncrementerRef>(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::<IncrementerRef>(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::<IncrementerRef>(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::<IncrementerRef>(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(())
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "forward_calls"
name = "updated_incrementer"
version = "4.0.1"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
Expand All @@ -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"]
Expand Down
Loading

0 comments on commit 16e45d3

Please sign in to comment.