mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-20 07:01:02 +00:00
Upgradable contracts using set_code function (#10690)
* poc logic * set_code_hash impl, tests, benchmark * Address @xgreenx's comments * Move func defs closer to set_storage * Check if code exists - increment/decrement codehash refcount * Document error for non-existing code hash * Revert unrelated change * Changes due to @athei's review * Fix error handling - comment errors: ReturnCodes - update mock ext implementation - return Error::CodeNotFound when no code for such hash * Emit ContractCodeUpdated when setting new code_hash * Address @athei's comments * Move related defs to the bottom * Minor comment update Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Improve docs * Improve docs * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Refactor set_code_hash test * Minor change to benchmark Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Minor change to benchmark Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Minor comment refactor Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Address @HCastano's comments * Update seal_set_code_hash comment Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * Move set_code_hash after delegate_call * Move function to the bottom * Moved and changed banchmark, added verify block * Bring back previous benchmark * Remove skip_meta for seal_set_code_hash * Bring back skip_meta for seal_set_storage_per_new_kb * Apply weights Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
83eed8018b
commit
e70ffbf44d
@@ -117,6 +117,22 @@ pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>) -> Result<(), Dispa
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increment the refcount of a code in-storage by one.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist.
|
||||
pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
|
||||
<OwnerInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
|
||||
if let Some(info) = existing {
|
||||
info.refcount = info.refcount.saturating_add(1);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<T>::CodeNotFound.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to remove code together with all associated information.
|
||||
pub fn try_remove<T: Config>(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
|
||||
<OwnerInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
|
||||
|
||||
@@ -26,7 +26,10 @@ mod runtime;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use self::code_cache::reinstrument;
|
||||
pub use self::runtime::{ReturnCode, Runtime, RuntimeCosts};
|
||||
pub use self::{
|
||||
code_cache::{decrement_refcount, increment_refcount},
|
||||
runtime::{ReturnCode, Runtime, RuntimeCosts},
|
||||
};
|
||||
use crate::{
|
||||
exec::{ExecResult, Executable, ExportedFunction, Ext},
|
||||
gas::GasMeter,
|
||||
@@ -322,6 +325,7 @@ mod tests {
|
||||
gas_meter: GasMeter<Test>,
|
||||
debug_buffer: Vec<u8>,
|
||||
ecdsa_recover: RefCell<Vec<([u8; 65], [u8; 32])>>,
|
||||
code_hashes: Vec<CodeHash<Test>>,
|
||||
}
|
||||
|
||||
/// The call is mocked and just returns this hardcoded value.
|
||||
@@ -332,6 +336,7 @@ mod tests {
|
||||
impl Default for MockExt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
code_hashes: Default::default(),
|
||||
storage: Default::default(),
|
||||
instantiates: Default::default(),
|
||||
terminations: Default::default(),
|
||||
@@ -390,6 +395,10 @@ mod tests {
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) },
|
||||
))
|
||||
}
|
||||
fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
|
||||
self.code_hashes.push(hash);
|
||||
Ok(())
|
||||
}
|
||||
fn transfer(&mut self, to: &AccountIdOf<Self::T>, value: u64) -> Result<(), DispatchError> {
|
||||
self.transfers.push(TransferEntry { to: to.clone(), value });
|
||||
Ok(())
|
||||
@@ -798,6 +807,67 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn contains_storage_works() {
|
||||
const CODE: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "__unstable__" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 4) size of input buffer (32 byte as we copy the key here)
|
||||
(data (i32.const 0) "\20")
|
||||
|
||||
;; [4, 36) input buffer
|
||||
|
||||
;; [36, inf) output buffer
|
||||
|
||||
(func (export "call")
|
||||
;; Receive key
|
||||
(call $seal_input
|
||||
(i32.const 4) ;; Pointer to the input buffer
|
||||
(i32.const 0) ;; Size of the length buffer
|
||||
)
|
||||
|
||||
;; Load the return value into the output buffer
|
||||
(i32.store (i32.const 36)
|
||||
(call $seal_contains_storage
|
||||
(i32.const 4) ;; The pointer to the storage key to fetch
|
||||
)
|
||||
)
|
||||
|
||||
;; Return the contents of the buffer
|
||||
(call $seal_return
|
||||
(i32.const 0) ;; flags
|
||||
(i32.const 36) ;; output buffer ptr
|
||||
(i32.const 4) ;; result is integer (4 bytes)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
let mut ext = MockExt::default();
|
||||
|
||||
ext.storage.insert([1u8; 32], vec![42u8]);
|
||||
ext.storage.insert([2u8; 32], vec![]);
|
||||
|
||||
// value does not exist -> sentinel value returned
|
||||
let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||
|
||||
// value did exist -> success
|
||||
let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1,);
|
||||
|
||||
// value did exist -> success (zero sized type)
|
||||
let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0,);
|
||||
}
|
||||
|
||||
const CODE_INSTANTIATE: &str = r#"
|
||||
(module
|
||||
;; seal_instantiate(
|
||||
@@ -2249,67 +2319,6 @@ mod tests {
|
||||
assert_eq!(&result.data.0[4..], &[0u8; 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn contains_storage_works() {
|
||||
const CODE: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "__unstable__" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 4) size of input buffer (32 byte as we copy the key here)
|
||||
(data (i32.const 0) "\20")
|
||||
|
||||
;; [4, 36) input buffer
|
||||
|
||||
;; [36, inf) output buffer
|
||||
|
||||
(func (export "call")
|
||||
;; Receive key
|
||||
(call $seal_input
|
||||
(i32.const 4) ;; Pointer to the input buffer
|
||||
(i32.const 0) ;; Size of the length buffer
|
||||
)
|
||||
|
||||
;; Load the return value into the output buffer
|
||||
(i32.store (i32.const 36)
|
||||
(call $seal_contains_storage
|
||||
(i32.const 4) ;; The pointer to the storage key to fetch
|
||||
)
|
||||
)
|
||||
|
||||
;; Return the contents of the buffer
|
||||
(call $seal_return
|
||||
(i32.const 0) ;; flags
|
||||
(i32.const 36) ;; output buffer ptr
|
||||
(i32.const 4) ;; result is integer (4 bytes)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
let mut ext = MockExt::default();
|
||||
|
||||
ext.storage.insert([1u8; 32], vec![42u8]);
|
||||
ext.storage.insert([2u8; 32], vec![]);
|
||||
|
||||
// value does not exist -> sentinel value returned
|
||||
let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL);
|
||||
|
||||
// value did exist -> success
|
||||
let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1,);
|
||||
|
||||
// value did exist -> success (zero sized type)
|
||||
let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn is_contract_works() {
|
||||
@@ -2385,4 +2394,45 @@ mod tests {
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(0u32.encode()) },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn set_code_hash() {
|
||||
const CODE: &str = r#"
|
||||
(module
|
||||
(import "__unstable__" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func $assert (param i32)
|
||||
(block $ok
|
||||
(br_if $ok
|
||||
(get_local 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(func (export "call")
|
||||
(local $exit_code i32)
|
||||
(set_local $exit_code
|
||||
(call $seal_set_code_hash (i32.const 0))
|
||||
)
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
|
||||
;; Hash of code.
|
||||
(data (i32.const 0)
|
||||
"\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11"
|
||||
"\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11"
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(CODE, [0u8; 32].encode(), &mut mock_ext).unwrap();
|
||||
|
||||
assert_eq!(mock_ext.code_hashes.pop().unwrap(), H256::from_slice(&[17u8; 32]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,9 @@ pub enum RuntimeCosts {
|
||||
/// Weight charged for calling into the runtime.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallRuntime(Weight),
|
||||
/// Weight of calling `seal_set_code_hash`
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
SetCodeHash,
|
||||
}
|
||||
|
||||
impl RuntimeCosts {
|
||||
@@ -305,6 +308,8 @@ impl RuntimeCosts {
|
||||
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallRuntime(weight) => weight,
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
SetCodeHash => s.set_code_hash,
|
||||
};
|
||||
RuntimeToken {
|
||||
#[cfg(test)]
|
||||
@@ -1960,4 +1965,41 @@ define_env!(Env, <E: Ext>,
|
||||
Err(_) => Ok(ReturnCode::EcdsaRecoverFailed),
|
||||
}
|
||||
},
|
||||
|
||||
// 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 address 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.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - code_hash_ptr: A pointer to the buffer that contains the new code hash.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// `ReturnCode::CodeNotFound`
|
||||
[__unstable__] seal_set_code_hash(ctx, code_hash_ptr: u32) -> ReturnCode => {
|
||||
ctx.charge_gas(RuntimeCosts::SetCodeHash)?;
|
||||
let code_hash: CodeHash<<E as Ext>::T> = ctx.read_sandbox_memory_as(code_hash_ptr)?;
|
||||
match ctx.ext.set_code_hash(code_hash) {
|
||||
Err(err) => {
|
||||
let code = Runtime::<E>::err_into_return_code(err)?;
|
||||
Ok(code)
|
||||
},
|
||||
Ok(()) => Ok(ReturnCode::Success)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user