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:
Yarik Bratashchuk
2022-02-11 09:46:51 +02:00
committed by GitHub
parent 83eed8018b
commit e70ffbf44d
11 changed files with 1111 additions and 785 deletions
+112 -62
View File
@@ -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]));
}
}