diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 889d763d249..706425390ef 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -563,3 +563,28 @@ where TypedEnvBackend::caller_is_origin::(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<()> { + ::on_instance(|instance| instance.set_code_hash(code_hash)) +} diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 1bbaa4c7e16..ede5d9bc472 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -282,6 +282,15 @@ pub trait EnvBackend { E: From, F: FnOnce(u32) -> ::core::result::Result<(), ErrorCode>, D: FnOnce(&[u8]) -> ::core::result::Result; + + /// 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. diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 969d7617469..0c4a43027f2 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -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 { diff --git a/crates/env/src/engine/on_chain/ext.rs b/crates/env/src/engine/on_chain/ext.rs index 13d63677daa..e949ebb07ad 100644 --- a/crates/env/src/engine/on_chain/ext.rs +++ b/crates/env/src/engine/on_chain/ext.rs @@ -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]>, @@ -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 { @@ -708,4 +716,4 @@ pub fn own_code_hash(output: &mut [u8]) { Ptr32Mut::from_ref(&mut output_len), ) } -} +} \ No newline at end of file diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index e6611b2367c..46aa162c3ba 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -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 { diff --git a/examples/upgradeable-contracts/README.md b/examples/upgradeable-contracts/README.md index e15846ed618..fd4773f3b68 100644 --- a/examples/upgradeable-contracts/README.md +++ b/examples/upgradeable-contracts/README.md @@ -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`. diff --git a/examples/upgradeable-contracts/set-code-hash/Cargo.toml b/examples/upgradeable-contracts/set-code-hash/Cargo.toml new file mode 100644 index 00000000000..9e8c38fb6ed --- /dev/null +++ b/examples/upgradeable-contracts/set-code-hash/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "incrementer" +version = "3.0.1" +edition = "2021" +authors = ["Parity Technologies "] +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 = [] + + diff --git a/examples/upgradeable-contracts/set-code-hash/lib.rs b/examples/upgradeable-contracts/set-code-hash/lib.rs new file mode 100644 index 00000000000..a5c5d1e97cd --- /dev/null +++ b/examples/upgradeable-contracts/set-code-hash/lib.rs @@ -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); + } + } +} diff --git a/examples/upgradeable-contracts/set-code-hash/new-increamenter/Cargo.toml b/examples/upgradeable-contracts/set-code-hash/new-increamenter/Cargo.toml new file mode 100644 index 00000000000..3356a384db3 --- /dev/null +++ b/examples/upgradeable-contracts/set-code-hash/new-increamenter/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "updated-incrementer" +version = "3.0.1" +edition = "2021" +authors = ["Parity Technologies "] +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 = [] diff --git a/examples/upgradeable-contracts/set-code-hash/new-increamenter/lib.rs b/examples/upgradeable-contracts/set-code-hash/new-increamenter/lib.rs new file mode 100644 index 00000000000..5786b743362 --- /dev/null +++ b/examples/upgradeable-contracts/set-code-hash/new-increamenter/lib.rs @@ -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); + } + } +}