Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration-test for possible migration pattern #1909

Merged
merged 20 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>"]
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 = []
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 = contract.call::<Incrementer>();

let get = call.get();
let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await;
assert_eq!(get_res.return_value(), 0);

let inc = call.inc();
let _inc_result = client
.call(&ink_e2e::alice(), &inc)
.submit()
.await
.expect("`inc` failed");

let get = call.get();
let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await;
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.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::<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::<updated_incrementer::incrementer::Incrementer>()
.inc();

let _inc_result = client
.call(&ink_e2e::alice(), &inc)
.submit()
.await
.expect("`inc` failed");

let get = call.get();
let get_res = client.call(&ink_e2e::alice(), &get).dry_run().await;

// Remember, we updated our incrementer contract to increment by `4`.
assert_eq!(
get_res.return_value(),
pre_migration_value + NEW_INC_BY as u32
);

Ok(())
}
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 an `Incrementer`
ascjones marked this conversation as resolved.
Show resolved Hide resolved
//! 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;
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 <[email protected]>"]
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 = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#![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. Using `&mut self`
/// would overwrite our migration changes with the contents of the
/// original `Incrementer`.
#[ink(message)]
pub fn migrate(&self, inc_by: u8, code_hash: Hash) {
let incrementer_new = IncrementerNew {
count: self.count as u64,
inc_by,
};
const STORAGE_KEY: u32 = 0x00000000;
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:?}")
})
}
}
}
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 <[email protected]>"]
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 = []
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading