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 set_code_hash function and example #1203

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)]
cmichi marked this conversation as resolved.
Show resolved Hide resolved
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)]
cmichi marked this conversation as resolved.
Show resolved Hide resolved
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)]
cmichi marked this conversation as resolved.
Show resolved Hide resolved
pub struct Incrementer {
count: u32,
}

impl Incrementer {
/// Creates a new counter smart contract initialized with the given base value.
cmichi marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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)]
cmichi marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}