diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 1622e92c6d..5f84085749 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -711,22 +711,23 @@ mod tests { ;; input_data_len: u32 ;; ) -> u32 (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) - (import "env" "ext_input_size" (func $ext_input_size (result i32))) - (import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32))) + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "deploy") ) (func (export "call") (block $fail - ;; fail if ext_input_size != 4 + ;; load and check the input data (which is stored in the scratch buffer). + ;; fail if the input size is not != 4 (br_if $fail (i32.ne (i32.const 4) - (call $ext_input_size) + (call $ext_scratch_size) ) ) - (call $ext_input_copy + (call $ext_scratch_copy (i32.const 0) (i32.const 0) (i32.const 4) diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index ec529696a8..bbb4e19cb4 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), impl_name: create_runtime_str!("substrate-node"), authoring_version: 10, - spec_version: 96, - impl_version: 96, + spec_version: 97, + impl_version: 97, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/srml/contracts/COMPLEXITY.md b/substrate/srml/contracts/COMPLEXITY.md index d87525a982..319707021d 100644 --- a/substrate/srml/contracts/COMPLEXITY.md +++ b/substrate/srml/contracts/COMPLEXITY.md @@ -348,18 +348,6 @@ This function serializes the current block's timestamp into the scratch buffer. **complexity**: Assuming that the timestamp is of constant size, this function has constant complexity. -## ext_input_size - -**complexity**: This function is of constant complexity. - -## ext_input_copy - -This function copies slice of data from the input buffer to the sandbox memory. The calling code specifies the slice length. Execution of the function consists of the following steps: - -1. Storing a specified slice of the input data into the sandbox memory (see sandboxing memory set) - -**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required. - ## ext_scratch_size This function returns the size of the scratch buffer. diff --git a/substrate/srml/contracts/src/tests.rs b/substrate/srml/contracts/src/tests.rs index 61a81a4eb6..79243f5d84 100644 --- a/substrate/srml/contracts/src/tests.rs +++ b/substrate/srml/contracts/src/tests.rs @@ -527,8 +527,8 @@ const CODE_SET_RENT: &str = r#" (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) (import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32))) - (import "env" "ext_input_size" (func $ext_input_size (result i32))) - (import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32))) + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) ;; insert a value of 4 bytes into storage @@ -575,7 +575,7 @@ const CODE_SET_RENT: &str = r#" (func (export "call") (local $input_size i32) (set_local $input_size - (call $ext_input_size) + (call $ext_scratch_size) ) (block $IF_ELSE (block $IF_2 @@ -602,16 +602,16 @@ const CODE_SET_RENT: &str = r#" ;; Set call set_rent_allowance with input (func (export "deploy") (local $input_size i32) + (set_local $input_size + (call $ext_scratch_size) + ) (call $ext_set_storage (i32.const 0) (i32.const 1) (i32.const 0) (i32.const 4) ) - (set_local $input_size - (call $ext_input_size) - ) - (call $ext_input_copy + (call $ext_scratch_copy (i32.const 0) (i32.const 0) (get_local $input_size) @@ -631,7 +631,7 @@ const CODE_SET_RENT: &str = r#" "#; // Use test_hash_and_code test to get the actual hash if the code changed. -const HASH_SET_RENT: [u8; 32] = hex!("21d6b1d59aa6038fcad632488e9026893a1bbb48581774c771b8f24320697f05"); +const HASH_SET_RENT: [u8; 32] = hex!("69aedfb4f6c1c398e97f8a5204de0f95ad5e7dc3540960beab11a86c569fbfcf"); /// Input data for each call in set_rent code mod call { @@ -1041,8 +1041,12 @@ const CODE_RESTORATION: &str = r#" (func (export "call") (call $ext_dispatch_call - (i32.const 200) ;; Pointer to the start of encoded call buffer - (i32.const 115) ;; Length of the buffer + ;; Pointer to the start of the encoded call buffer + (i32.const 200) + ;; The length of the encoded call buffer. + ;; + ;; NB: This is required to keep in sync with the values in `restoration`. + (i32.const 115) ) ) (func (export "deploy") @@ -1069,16 +1073,17 @@ const CODE_RESTORATION: &str = r#" ;; ACL (data (i32.const 100) "\01") - ;; Call - (data (i32.const 200) "\01\05\02\00\00\00\00\00\00\00\21\d6\b1\d5\9a\a6\03\8f\ca\d6\32\48\8e\90" - "\26\89\3a\1b\bb\48\58\17\74\c7\71\b8\f2\43\20\69\7f\05\32\00\00\00\00\00\00\00\08\01\00\00" - "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01" - "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" - "\00" + ;; Serialized version of `T::Call` that encodes a call to `restore_to` function. For more + ;; details check out the `ENCODED_CALL_LITERAL`. + (data (i32.const 200) + "\01\05\02\00\00\00\00\00\00\00\69\ae\df\b4\f6\c1\c3\98\e9\7f\8a\52\04\de\0f\95\ad\5e\7d\c3" + "\54\09\60\be\ab\11\a8\6c\56\9f\bf\cf\32\00\00\00\00\00\00\00\08\01\00\00\00\00\00\00\00\00" + "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00" + "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" ) ) "#; -const HASH_RESTORATION: [u8; 32] = hex!("b393bfa8de97b02f08ba1580b46100a80c9489a97c8d870691c9ff7236b29bc7"); +const HASH_RESTORATION: [u8; 32] = hex!("02988182efba70fe605031f5c55bfa59e47f72c0a4707f22b6b74fffbf7803dc"); #[test] fn restorations_dirty_storage_and_different_storage() { @@ -1116,12 +1121,25 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: vec![acl_key, acl_key], )))); - let literal = "0105020000000000000021d6b1d59aa6038fcad632488e9026893a1bbb48581774c771b8f243206\ - 97f053200000000000000080100000000000000000000000000000000000000000000000000000000000000010\ - 0000000000000000000000000000000000000000000000000000000000000"; - - assert_eq!(encoded, literal); - assert_eq!(115, hex::decode(literal).unwrap().len()); + // `ENCODED_CALL_LITERAL` is encoded `T::Call` represented as a byte array. There is an exact + // same copy of this (modulo hex notation differences) in `CODE_RESTORATION`. + // + // When this assert is triggered make sure that you update the literals here and in + // `CODE_RESTORATION`. Hopefully, we switch to automatical injection of the code. + const ENCODED_CALL_LITERAL: &str = + "0105020000000000000069aedfb4f6c1c398e97f8a5204de0f95ad5e7dc3540960beab11a86c569fbfcf320000\ + 0000000000080100000000000000000000000000000000000000000000000000000000000000010000000000000\ + 0000000000000000000000000000000000000000000000000"; + assert_eq!( + encoded, + ENCODED_CALL_LITERAL, + "The literal was changed and requires updating here and in `CODE_RESTORATION`", + ); + assert_eq!( + hex::decode(ENCODED_CALL_LITERAL).unwrap().len(), + 115, + "The size of the literal was changed and requires updating in `CODE_RESTORATION`", + ); let restoration_wasm = wabt::wat2wasm(CODE_RESTORATION).unwrap(); let set_rent_wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap(); @@ -1153,14 +1171,18 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: }, ]); + // Create an account with address `BOB` with code `HASH_SET_RENT`. + // The input parameter sets the rent allowance to 0. assert_ok!(Contract::create( Origin::signed(ALICE), 30_000, - 100_000, HASH_SET_RENT.into(), + 100_000, + HASH_SET_RENT.into(), ::Balance::from(0u32).encode() )); - // Check creation + // Check if `BOB` was created successfully and that the rent allowance is + // set to 0. let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); assert_eq!(bob_contract.rent_allowance, 0); @@ -1172,36 +1194,49 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: ); } - // Advance 4 blocks + // Advance 4 blocks, to the 5th. System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - // Trigger rent through call + // Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0 + // we expect that it will get removed leaving tombstone. assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + /// Create another account with the address `DJANGO` with `CODE_RESTORATION`. + /// + /// Note that we can't use `ALICE` for creating `DJANGO` so we create yet another + /// account `CHARLIE` and create `DJANGO` with it. Balances::deposit_creating(&CHARLIE, 1_000_000); assert_ok!(Contract::create( Origin::signed(CHARLIE), 30_000, - 100_000, HASH_RESTORATION.into(), + 100_000, + HASH_RESTORATION.into(), ::Balance::from(0u32).encode() )); + // Before performing a call to `DJANGO` save its original trie id. let django_trie_id = ContractInfoOf::::get(DJANGO).unwrap() .get_alive().unwrap().trie_id; if !test_restore_to_with_dirty_storage { - // Advance 1 blocks + // Advance 1 block, to the 6th. System::initialize(&6, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); } + // Perform a call to `DJANGO`. This should either perform restoration successfully or + // fail depending on the test parameters. assert_ok!(Contract::call( Origin::signed(ALICE), - DJANGO, 0, 100_000, + DJANGO, + 0, + 100_000, vec![], )); if test_different_storage || test_restore_to_with_dirty_storage { + // Parametrization of the test imply restoration failure. Check that `DJANGO` aka + // restoration contract is still in place and also that `BOB` doesn't exist. assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); let django_contract = ContractInfoOf::::get(DJANGO).unwrap() .get_alive().unwrap(); @@ -1209,6 +1244,8 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: assert_eq!(django_contract.trie_id, django_trie_id); assert_eq!(django_contract.deduct_block, System::block_number()); } else { + // Here we expect that the restoration is succeeded. Check that the restoration + // contract `DJANGO` ceased to exist and that `BOB` returned back. let bob_contract = ContractInfoOf::::get(BOB).unwrap() .get_alive().unwrap(); assert_eq!(bob_contract.rent_allowance, 50); diff --git a/substrate/srml/contracts/src/wasm/mod.rs b/substrate/srml/contracts/src/wasm/mod.rs index 7dcd16d5da..28f71dc9df 100644 --- a/substrate/srml/contracts/src/wasm/mod.rs +++ b/substrate/srml/contracts/src/wasm/mod.rs @@ -134,7 +134,7 @@ impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a, T> { let mut runtime = Runtime::new( ext, - input_data, + input_data.to_vec(), empty_output_buf, &self.schedule, memory, diff --git a/substrate/srml/contracts/src/wasm/runtime.rs b/substrate/srml/contracts/src/wasm/runtime.rs index 873464c5be..e90344b69d 100644 --- a/substrate/srml/contracts/src/wasm/runtime.rs +++ b/substrate/srml/contracts/src/wasm/runtime.rs @@ -39,9 +39,8 @@ enum SpecialTrap { } /// Can only be used for one call. -pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> { +pub(crate) struct Runtime<'a, E: Ext + 'a> { ext: &'a mut E, - input_data: &'data [u8], // A VM can return a result only once and only by value. So // we wrap output buffer to make it possible to take the buffer out. empty_output_buf: Option, @@ -51,10 +50,10 @@ pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> { gas_meter: &'a mut GasMeter, special_trap: Option, } -impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { +impl<'a, E: Ext + 'a> Runtime<'a, E> { pub(crate) fn new( ext: &'a mut E, - input_data: &'data [u8], + input_data: Vec, empty_output_buf: EmptyOutputBuf, schedule: &'a Schedule<::Gas>, memory: sandbox::Memory, @@ -62,9 +61,9 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { ) -> Self { Runtime { ext, - input_data, empty_output_buf: Some(empty_output_buf), - scratch_buf: Vec::new(), + // Put the input data into the scratch buffer immediately. + scratch_buf: input_data, schedule, memory, gas_meter, @@ -443,6 +442,8 @@ define_env!(Env, , // Save a data buffer as a result of the execution, terminate the execution and return a // successful result to the caller. + // + // This is the only way to return a data buffer to the caller. ext_return(ctx, data_ptr: u32, data_len: u32) => { match ctx .gas_meter @@ -573,45 +574,19 @@ define_env!(Env, , Ok(()) }, - // Returns the size of the input buffer. - ext_input_size(ctx) -> u32 => { - Ok(ctx.input_data.len() as u32) - }, - - // Copy data from the input buffer starting from `offset` with length `len` into the contract memory. - // The region at which the data should be put is specified by `dest_ptr`. - ext_input_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => { - let offset = offset as usize; - if offset > ctx.input_data.len() { - // Offset can't be larger than input buffer length. - return Err(sandbox::HostError); - } - - // This can't panic since `offset <= ctx.input_data.len()`. - let src = &ctx.input_data[offset..]; - if src.len() != len as usize { - return Err(sandbox::HostError); - } - - // Finally, perform the write. - write_sandbox_memory( - ctx.schedule, - ctx.gas_meter, - &ctx.memory, - dest_ptr, - src, - )?; - - Ok(()) - }, - // Returns the size of the scratch buffer. + // + // For more details on the scratch buffer see `ext_scratch_copy`. ext_scratch_size(ctx) -> u32 => { Ok(ctx.scratch_buf.len() as u32) }, // Copy data from the scratch buffer starting from `offset` with length `len` into the contract memory. // The region at which the data should be put is specified by `dest_ptr`. + // + // In order to get size of the scratch buffer use `ext_scratch_size`. At the start of contract + // execution, the scratch buffer is filled with the input data. Whenever a contract calls + // function that uses the scratch buffer the contents of the scratch buffer are overwritten. ext_scratch_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => { let offset = offset as usize; if offset > ctx.scratch_buf.len() {