-
Notifications
You must be signed in to change notification settings - Fork 431
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
integration-test
for possible migration pattern (#1909)
* WIP * Update versions * WIP * WIP migration * WIP * Make test pass * Move e2e tests mod to own file * Update comment * Update example for new e2e API * Update integration-tests/upgradeable-contracts/set-code-hash-migration/lib.rs Co-authored-by: Michael Müller <michi@parity.io> * Top level gitignore * Fix tests update comments * Update upgradeable contracts README.md * spelling --------- Co-authored-by: Michael Müller <michi@parity.io>
- Loading branch information
Showing
9 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
**/target/ | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
integration-tests/upgradeable-contracts/set-code-hash-migration/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[package] | ||
name = "incrementer" | ||
version = "5.0.0-alpha" | ||
authors = ["Parity Technologies <admin@parity.io>"] | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
ink = { path = "../../../crates/ink", 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 = { path = "../../../crates/e2e" } | ||
|
||
[lib] | ||
path = "lib.rs" | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"ink/std", | ||
"migration/std", | ||
"updated-incrementer/std", | ||
] | ||
ink-as-dependency = [] | ||
e2e-tests = [] |
94 changes: 94 additions & 0 deletions
94
integration-tests/upgradeable-contracts/set-code-hash-migration/e2e_tests.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use super::incrementer::*; | ||
use ink_e2e::ContractsBackend; | ||
|
||
type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>; | ||
|
||
#[ink_e2e::test] | ||
async fn migration_works<Client: E2EBackend>(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::<Incrementer>(); | ||
|
||
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::<migration::incrementer::Incrementer>() | ||
.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::<updated_incrementer::incrementer::Incrementer>() | ||
.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(()) | ||
} |
69 changes: 69 additions & 0 deletions
69
integration-tests/upgradeable-contracts/set-code-hash-migration/lib.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
19 changes: 19 additions & 0 deletions
19
integration-tests/upgradeable-contracts/set-code-hash-migration/migration/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "migration" | ||
version = "5.0.0-alpha" | ||
authors = ["Parity Technologies <admin@parity.io>"] | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
ink = { path = "../../../../crates/ink", default-features = false } | ||
|
||
[lib] | ||
path = "lib.rs" | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"ink/std", | ||
] | ||
ink-as-dependency = [] |
70 changes: 70 additions & 0 deletions
70
integration-tests/upgradeable-contracts/set-code-hash-migration/migration/lib.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = | ||
<Incrementer as ink::storage::traits::StorageKey>::KEY; | ||
ink::env::set_contract_storage(&STORAGE_KEY, &incrementer_new); | ||
|
||
ink::env::set_code_hash::<<Self as ink::env::ContractEnv>::Env>(&code_hash) | ||
.unwrap_or_else(|err| { | ||
panic!("Failed to `set_code_hash` to {code_hash:?} due to {err:?}") | ||
}) | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...ration-tests/upgradeable-contracts/set-code-hash-migration/updated-incrementer/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "updated-incrementer" | ||
version = "5.0.0-alpha" | ||
authors = ["Parity Technologies <admin@parity.io>"] | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
ink = { path = "../../../../crates/ink", default-features = false } | ||
|
||
[lib] | ||
path = "lib.rs" | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"ink/std", | ||
] | ||
ink-as-dependency = [] |
Oops, something went wrong.