Skip to content

Commit

Permalink
Add set_code_hash function and example (#1203)
Browse files Browse the repository at this point in the history
* Add a function `ink_env::set_code_hash`

* Add an example of `ink_env::set_code_hash`.

* Fix spelling mistake

* Apply suggestions from code review

Co-authored-by: Michael Müller <mich@elmueller.net>
  • Loading branch information
willser and cmichi authored Apr 22, 2022
1 parent 81cdc6b commit 218c91c
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 1 deletion.
25 changes: 25 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,28 @@ where
TypedEnvBackend::caller_is_origin::<E>(instance)
})
}

/// Replace the contract code at the specified address with new code.
///
/// # Note
///
/// There are a couple of important considerations which must be taken into account when
/// using this API:
///
/// 1. The storage at the code hash will remain untouched. This means that contract developers
/// must ensure that the storage layout of the new code is compatible with that of the old code.
///
/// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another way,
/// when using this API you lose the guarantee that an address always identifies a specific code hash.
///
/// 3. If a contract calls into itself after changing its code the new call would use
/// the new code. However, if the original caller panics after returning from the sub call it
/// would revert the changes made by `seal_set_code_hash` and the next caller would use
/// the old code.
///
/// # Errors
///
/// `ReturnCode::CodeNotFound` in case the supplied `code_hash` cannot be found on-chain.
pub fn set_code_hash(code_hash: &[u8; 32]) -> Result<()> {
<EnvInstance as OnInstance>::on_instance(|instance| instance.set_code_hash(code_hash))
}
9 changes: 9 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,15 @@ pub trait EnvBackend {
E: From<ErrorCode>,
F: FnOnce(u32) -> ::core::result::Result<(), ErrorCode>,
D: FnOnce(&[u8]) -> ::core::result::Result<T, E>;

/// Sets a new code hash for the current contract.
///
/// This effectively replaces the code which is executed for this contract address.
///
/// # Errors
///
/// - If the supplied `code_hash` cannot be found on-chain.
fn set_code_hash(&mut self, code_hash: &[u8]) -> Result<()>;
}

/// Environmental contract functionality.
Expand Down
4 changes: 4 additions & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ impl EnvBackend for EnvInstance {
let decoded = decode_to_result(&out[..])?;
Ok(decoded)
}

fn set_code_hash(&mut self, _code_hash: &[u8]) -> Result<()> {
unimplemented!("off-chain environment does not support `set_code_hash`")
}
}

impl TypedEnvBackend for EnvInstance {
Expand Down
10 changes: 9 additions & 1 deletion crates/env/src/engine/on_chain/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ mod sys {
output_ptr: Ptr32Mut<[u8]>,
) -> ReturnCode;


pub fn seal_set_code_hash(code_hash_ptr: Ptr32<[u8]>) -> ReturnCode;

pub fn seal_code_hash(
account_id_ptr: Ptr32<[u8]>,
output_ptr: Ptr32Mut<[u8]>,
Expand Down Expand Up @@ -688,6 +691,11 @@ pub fn caller_is_origin() -> bool {
ret_val.into_bool()
}

pub fn set_code_hash(code_hash: &[u8]) -> Result {
let ret_val = unsafe { sys::seal_set_code_hash(Ptr32::from_slice(code_hash)) };
ret_val.into()
}

pub fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result {
let mut output_len = output.len() as u32;
let ret_val = unsafe {
Expand All @@ -708,4 +716,4 @@ pub fn own_code_hash(output: &mut [u8]) {
Ptr32Mut::from_ref(&mut output_len),
)
}
}
}
4 changes: 4 additions & 0 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ impl EnvBackend for EnvInstance {
let decoded = decode_to_result(&output[..])?;
Ok(decoded)
}

fn set_code_hash(&mut self, code_hash_ptr: &[u8]) -> Result<()> {
ext::set_code_hash(code_hash_ptr).map_err(Into::into)
}
}

impl TypedEnvBackend for EnvInstance {
Expand Down
5 changes: 5 additions & 0 deletions examples/upgradeable-contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ more information on proxy patterns.
* Executes any call that does not match a selector of itself with the code of another contract.
* The other contract does not need to be deployed on-chain.
* State is stored in the storage of the originally called contract.


## [`set-code-hash`](https://github.com/paritytech/ink/tree/master/examples/upgradeable-contracts/set-code-hash)

* Update contract code by `set_code_hash`.
39 changes: 39 additions & 0 deletions examples/upgradeable-contracts/set-code-hash/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "incrementer"
version = "3.0.1"
edition = "2021"
authors = ["Parity Technologies <admin@parity.io>"]
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ink_primitives = { version = "3.0.1", path = "../../../crates/primitives", default-features = false }
ink_prelude = { version = "3.0.1", path = "../../../crates/prelude", default-features = false }
ink_metadata = { version = "3.0.1", path = "../../../crates/metadata", default-features = false, features = ["derive"], optional = true }
ink_env = { version = "3.0.1", path = "../../../crates/env", default-features = false }
ink_storage = { version = "3.0.1", path = "../../../crates/storage", default-features = false }
ink_lang = { version = "3.0.1", path = "../../../crates/lang", default-features = false }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }

[lib]
name = "incrementer"
path = "lib.rs"
crate-type = ["cdylib"]

[features]
default = ["std"]
std = [
"ink_primitives/std",
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_lang/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []


57 changes: 57 additions & 0 deletions examples/upgradeable-contracts/set-code-hash/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[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 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 {:?} due to {:?}",
code_hash, err
)
});
ink_env::debug_println!("Switched code hash to {:?}.", code_hash);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "updated-incrementer"
version = "3.0.1"
edition = "2021"
authors = ["Parity Technologies <admin@parity.io>"]
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ink_primitives = { version = "3.0.1", path = "../../../../crates/primitives", default-features = false }
ink_metadata = { version = "3.0.1", path = "../../../../crates/metadata", default-features = false, features = ["derive"], optional = true }
ink_env = { version = "3.0.1", path = "../../../../crates/env", default-features = false, features = ["ink-debug"] }
ink_storage = { version = "3.0.1", path = "../../../../crates/storage", default-features = false }
ink_lang = { version = "3.0.1", path = "../../../../crates/lang", default-features = false }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }

[lib]
name = "updated_incrementer"
path = "lib.rs"
crate-type = ["cdylib"]

[features]
default = ["std"]
std = [
"ink_primitives/std",
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_lang/std",
"scale/std",
"scale-info/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)]

use ink_lang as ink;

#[ink::contract]
pub mod incrementer {

/// This struct contains the smart contract storage.
///
/// *Note:* We use exactly the same storage struct as in the originally deployed `incrementer`.
#[ink(storage)]
pub struct Incrementer {
count: u32,
}

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`.
#[ink(constructor)]
pub fn default() -> Self {
Self::new(0)
}

/// 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`.
#[ink(message)]
pub fn inc(&mut self) {
self.count += 4;
ink_env::debug_println!("The new count is {}, it was modified using the updated `new_incrementer` 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 {:?} due to {:?}",
code_hash, err
)
});
ink_env::debug_println!("Switched code hash to {:?}.", code_hash);
}
}
}

0 comments on commit 218c91c

Please sign in to comment.