diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index af1812f34d..a8957e9016 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -79,7 +79,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 123, + spec_version: 125, impl_version: 125, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/srml/contracts/src/exec.rs b/substrate/srml/contracts/src/exec.rs index 084558e76f..b27116a1ac 100644 --- a/substrate/srml/contracts/src/exec.rs +++ b/substrate/srml/contracts/src/exec.rs @@ -90,6 +90,15 @@ pub trait Ext { /// Notes a call dispatch. fn note_dispatch_call(&mut self, call: CallOf); + /// Notes a restoration request. + fn note_restore_to( + &mut self, + dest: AccountIdOf, + code_hash: CodeHash, + rent_allowance: BalanceOf, + delta: Vec, + ); + /// Returns a reference to the account id of the caller. fn caller(&self) -> &AccountIdOf; @@ -254,13 +263,41 @@ impl Token for ExecFeeToken { } } +#[cfg_attr(any(feature = "std", test), derive(Debug, PartialEq, Eq, Clone))] +pub enum DeferredAction { + DepositEvent { + /// A list of topics this event will be deposited with. + topics: Vec, + /// The event to deposit. + event: Event, + }, + DispatchRuntimeCall { + /// The account id of the contract who dispatched this call. + origin: T::AccountId, + /// The call to dispatch. + call: T::Call, + }, + RestoreTo { + /// The account id of the contract which is removed during the restoration and transfers + /// its storage to the restored contract. + donor: T::AccountId, + /// The account id of the restored contract. + dest: T::AccountId, + /// The code hash of the restored contract. + code_hash: CodeHash, + /// The initial rent allowance to set. + rent_allowance: BalanceOf, + /// The keys to delete upon restoration. + delta: Vec, + }, +} + pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { pub self_account: T::AccountId, pub self_trie_id: Option, pub overlay: OverlayAccountDb<'a, T>, pub depth: usize, - pub events: Vec>, - pub calls: Vec<(T::AccountId, T::Call)>, + pub deferred: Vec>, pub config: &'a Config, pub vm: &'a V, pub loader: &'a L, @@ -284,8 +321,7 @@ where self_account: origin, overlay: OverlayAccountDb::::new(&DirectAccountDb), depth: 0, - events: Vec::new(), - calls: Vec::new(), + deferred: Vec::new(), config: &cfg, vm: &vm, loader: &loader, @@ -302,8 +338,7 @@ where self_account: dest, overlay: OverlayAccountDb::new(&self.overlay), depth: self.depth + 1, - events: Vec::new(), - calls: Vec::new(), + deferred: Vec::new(), config: self.config, vm: self.vm, loader: self.loader, @@ -435,7 +470,7 @@ where .into_result()?; // Deposit an instantiation event. - nested.events.push(IndexedEvent { + nested.deferred.push(DeferredAction::DepositEvent { event: RawEvent::Instantiated(caller.clone(), dest.clone()), topics: Vec::new(), }); @@ -464,15 +499,14 @@ where -> Result, &'static str> where F: FnOnce(&mut ExecutionContext) -> Result, &'static str> { - let (output_data, change_set, events, calls) = { + let (output_data, change_set, deferred) = { let mut nested = self.nested(dest, trie_id); let output_data = func(&mut nested)?; - (output_data, nested.overlay.into_change_set(), nested.events, nested.calls) + (output_data, nested.overlay.into_change_set(), nested.deferred) }; self.overlay.commit(change_set); - self.events.extend(events); - self.calls.extend(calls); + self.deferred.extend(deferred); Ok(output_data) } @@ -592,7 +626,7 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( if transactor != dest { ctx.overlay.set_balance(transactor, new_from_balance); ctx.overlay.set_balance(dest, new_to_balance); - ctx.events.push(IndexedEvent { + ctx.deferred.push(DeferredAction::DepositEvent { event: RawEvent::Transfer(transactor.clone(), dest.clone(), value), topics: Vec::new(), }); @@ -656,11 +690,27 @@ where .call(to.clone(), value, gas_meter, input_data, empty_output_buf) } - /// Notes a call dispatch. fn note_dispatch_call(&mut self, call: CallOf) { - self.ctx.calls.push( - (self.ctx.self_account.clone(), call) - ); + self.ctx.deferred.push(DeferredAction::DispatchRuntimeCall { + origin: self.ctx.self_account.clone(), + call, + }); + } + + fn note_restore_to( + &mut self, + dest: AccountIdOf, + code_hash: CodeHash, + rent_allowance: BalanceOf, + delta: Vec, + ) { + self.ctx.deferred.push(DeferredAction::RestoreTo { + donor: self.ctx.self_account.clone(), + dest, + code_hash, + rent_allowance, + delta, + }); } fn address(&self) -> &T::AccountId { @@ -688,7 +738,7 @@ where } fn deposit_event(&mut self, topics: Vec, data: Vec) { - self.ctx.events.push(IndexedEvent { + self.ctx.deferred.push(DeferredAction::DepositEvent { topics, event: RawEvent::Contract(self.ctx.self_account.clone(), data), }); @@ -724,7 +774,7 @@ where mod tests { use super::{ BalanceOf, ExecFeeToken, ExecutionContext, Ext, Loader, EmptyOutputBuf, TransferFeeKind, TransferFeeToken, - Vm, VmExecResult, InstantiateReceipt, RawEvent, IndexedEvent, + Vm, VmExecResult, InstantiateReceipt, RawEvent, DeferredAction, }; use crate::account_db::AccountDb; use crate::gas::GasMeter; @@ -741,6 +791,21 @@ mod tests { const BOB: u64 = 2; const CHARLIE: u64 = 3; + impl<'a, T, V, L> ExecutionContext<'a, T, V, L> + where T: crate::Trait + { + fn events(&self) -> Vec> { + self.deferred + .iter() + .filter(|action| match *action { + DeferredAction::DepositEvent { .. } => true, + _ => false, + }) + .cloned() + .collect() + } + } + struct MockCtx<'a> { ext: &'a mut dyn Ext, input_data: &'a [u8], @@ -1326,12 +1391,12 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!(ctx.overlay.get_code_hash(&created_contract_address).unwrap(), dummy_ch); - assert_eq!(&ctx.events, &[ - IndexedEvent { + assert_eq!(&ctx.events(), &[ + DeferredAction::DepositEvent { event: RawEvent::Transfer(ALICE, created_contract_address, 100), topics: Vec::new(), }, - IndexedEvent { + DeferredAction::DepositEvent { event: RawEvent::Instantiated(ALICE, created_contract_address), topics: Vec::new(), } @@ -1384,16 +1449,16 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!(ctx.overlay.get_code_hash(&created_contract_address).unwrap(), dummy_ch); - assert_eq!(&ctx.events, &[ - IndexedEvent { + assert_eq!(&ctx.events(), &[ + DeferredAction::DepositEvent { event: RawEvent::Transfer(ALICE, BOB, 20), topics: Vec::new(), }, - IndexedEvent { + DeferredAction::DepositEvent { event: RawEvent::Transfer(BOB, created_contract_address, 15), topics: Vec::new(), }, - IndexedEvent { + DeferredAction::DepositEvent { event: RawEvent::Instantiated(BOB, created_contract_address), topics: Vec::new(), }, @@ -1441,8 +1506,8 @@ mod tests { // The contract wasn't created so we don't expect to see an instantiation // event here. - assert_eq!(&ctx.events, &[ - IndexedEvent { + assert_eq!(&ctx.events(), &[ + DeferredAction::DepositEvent { event: RawEvent::Transfer(ALICE, BOB, 20), topics: Vec::new(), }, diff --git a/substrate/srml/contracts/src/lib.rs b/substrate/srml/contracts/src/lib.rs index c4e588e12e..15d7b37a45 100644 --- a/substrate/srml/contracts/src/lib.rs +++ b/substrate/srml/contracts/src/lib.rs @@ -621,84 +621,6 @@ decl_module! { } } - /// Allows a contract to restore a tombstone by giving its storage. - /// - /// The contract that wants to restore (i.e. origin of the call, or `msg.sender` in Solidity terms) will compute a - /// tombstone with its storage and the given code_hash. If the computed tombstone - /// match the destination one, the destination contract is restored with the rent_allowance` specified, - /// while the origin sends all its funds to the destination and is removed. - fn restore_to( - origin, - dest: T::AccountId, - code_hash: CodeHash, - rent_allowance: BalanceOf, - delta: Vec - ) { - let origin = ensure_signed(origin)?; - - let mut origin_contract = >::get(&origin) - .and_then(|c| c.get_alive()) - .ok_or("Cannot restore from inexisting or tombstone contract")?; - - let current_block = >::block_number(); - - if origin_contract.last_write == Some(current_block) { - return Err("Origin TrieId written in the current block"); - } - - let dest_tombstone = >::get(&dest) - .and_then(|c| c.get_tombstone()) - .ok_or("Cannot restore to inexisting or alive contract")?; - - let last_write = if !delta.is_empty() { - Some(current_block) - } else { - origin_contract.last_write - }; - - let key_values_taken = delta.iter() - .filter_map(|key| { - child::get_raw(&origin_contract.trie_id, &blake2_256(key)).map(|value| { - child::kill(&origin_contract.trie_id, &blake2_256(key)); - (key, value) - }) - }) - .collect::>(); - - let tombstone = >::new( - // This operation is cheap enough because last_write (delta not included) - // is not this block as it has been checked earlier. - &runtime_io::child_storage_root(&origin_contract.trie_id)[..], - code_hash, - ); - - if tombstone != dest_tombstone { - for (key, value) in key_values_taken { - child::put_raw(&origin_contract.trie_id, &blake2_256(key), &value); - } - - return Err("Tombstones don't match"); - } - - origin_contract.storage_size -= key_values_taken.iter() - .map(|(_, value)| value.len() as u32) - .sum::(); - - >::remove(&origin); - >::insert(&dest, ContractInfo::Alive(RawAliveContractInfo { - trie_id: origin_contract.trie_id, - storage_size: origin_contract.storage_size, - code_hash, - rent_allowance, - deduct_block: current_block, - last_write, - })); - - let origin_free_balance = T::Currency::free_balance(&origin); - T::Currency::make_free_balance_be(&origin, >::zero()); - T::Currency::deposit_creating(&dest, origin_free_balance); - } - fn on_finalize() { GasSpent::kill(); } @@ -727,14 +649,6 @@ impl Module { if result.is_ok() { // Commit all changes that made it thus far into the persistent storage. DirectAccountDb.commit(ctx.overlay.into_change_set()); - - // Then deposit all events produced. - ctx.events.into_iter().for_each(|indexed_event| { - >::deposit_event_indexed( - &*indexed_event.topics, - ::Event::from(indexed_event.event).into(), - ); - }); } // Refund cost of the unused gas. @@ -743,14 +657,110 @@ impl Module { // can alter the balance of the caller. gas::refund_unused_gas::(&origin, gas_meter, imbalance); - // Dispatch every recorded call with an appropriate origin. - ctx.calls.into_iter().for_each(|(who, call)| { - let result = call.dispatch(RawOrigin::Signed(who.clone()).into()); - Self::deposit_event(RawEvent::Dispatched(who, result.is_ok())); + // Execute deferred actions. + ctx.deferred.into_iter().for_each(|deferred| { + use self::exec::DeferredAction::*; + match deferred { + DepositEvent { + topics, + event, + } => >::deposit_event_indexed( + &*topics, + ::Event::from(event).into(), + ), + DispatchRuntimeCall { + origin: who, + call, + } => { + let result = call.dispatch(RawOrigin::Signed(who.clone()).into()); + Self::deposit_event(RawEvent::Dispatched(who, result.is_ok())); + } + RestoreTo { + donor, + dest, + code_hash, + rent_allowance, + delta, + } => { + let _result = Self::restore_to(donor, dest, code_hash, rent_allowance, delta); + } + } }); result } + + fn restore_to( + origin: T::AccountId, + dest: T::AccountId, + code_hash: CodeHash, + rent_allowance: BalanceOf, + delta: Vec + ) -> Result { + let mut origin_contract = >::get(&origin) + .and_then(|c| c.get_alive()) + .ok_or("Cannot restore from inexisting or tombstone contract")?; + + let current_block = >::block_number(); + + if origin_contract.last_write == Some(current_block) { + return Err("Origin TrieId written in the current block"); + } + + let dest_tombstone = >::get(&dest) + .and_then(|c| c.get_tombstone()) + .ok_or("Cannot restore to inexisting or alive contract")?; + + let last_write = if !delta.is_empty() { + Some(current_block) + } else { + origin_contract.last_write + }; + + let key_values_taken = delta.iter() + .filter_map(|key| { + child::get_raw(&origin_contract.trie_id, &blake2_256(key)).map(|value| { + child::kill(&origin_contract.trie_id, &blake2_256(key)); + (key, value) + }) + }) + .collect::>(); + + let tombstone = >::new( + // This operation is cheap enough because last_write (delta not included) + // is not this block as it has been checked earlier. + &runtime_io::child_storage_root(&origin_contract.trie_id)[..], + code_hash, + ); + + if tombstone != dest_tombstone { + for (key, value) in key_values_taken { + child::put_raw(&origin_contract.trie_id, &blake2_256(key), &value); + } + + return Err("Tombstones don't match"); + } + + origin_contract.storage_size -= key_values_taken.iter() + .map(|(_, value)| value.len() as u32) + .sum::(); + + >::remove(&origin); + >::insert(&dest, ContractInfo::Alive(RawAliveContractInfo { + trie_id: origin_contract.trie_id, + storage_size: origin_contract.storage_size, + code_hash, + rent_allowance, + deduct_block: current_block, + last_write, + })); + + let origin_free_balance = T::Currency::free_balance(&origin); + T::Currency::make_free_balance_be(&origin, >::zero()); + T::Currency::deposit_creating(&dest, origin_free_balance); + + Ok(()) + } } decl_event! { diff --git a/substrate/srml/contracts/src/tests.rs b/substrate/srml/contracts/src/tests.rs index f4699a8c1a..5a53d8426f 100644 --- a/substrate/srml/contracts/src/tests.rs +++ b/substrate/srml/contracts/src/tests.rs @@ -1256,17 +1256,24 @@ fn default_rent_allowance_on_create() { const CODE_RESTORATION: &str = r#" (module (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) - (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) + (import "env" "ext_restore_to" (func $ext_restore_to (param i32 i32 i32 i32 i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_dispatch_call - ;; 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) + (call $ext_restore_to + ;; Pointer and length of the encoded dest buffer. + (i32.const 256) + (i32.const 8) + ;; Pointer and length of the encoded code hash buffer + (i32.const 264) + (i32.const 32) + ;; Pointer and length of the encoded rent_allowance buffer + (i32.const 296) + (i32.const 8) + ;; Pointer and number of items in the delta buffer. + ;; This buffer specifies multiple keys for removal before restoration. + (i32.const 100) + (i32.const 1) ) ) (func (export "deploy") @@ -1290,17 +1297,20 @@ const CODE_RESTORATION: &str = r#" ;; Data to restore (data (i32.const 0) "\28") - ;; ACL + ;; Buffer that has ACL storage keys. (data (i32.const 100) "\01") - ;; 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" + ;; Address of bob + (data (i32.const 256) "\02\00\00\00\00\00\00\00") + + ;; Code hash of SET_CODE + (data (i32.const 264) + "\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" ) + + ;; Rent allowance + (data (i32.const 296) "\32\00\00\00\00\00\00\00") ) "#; @@ -1325,45 +1335,9 @@ fn restoration_success() { } fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: bool) { + let (set_rent_wasm, set_rent_code_hash) = compile_module::(CODE_SET_RENT).unwrap(); let (restoration_wasm, restoration_code_hash) = compile_module::(CODE_RESTORATION).unwrap(); - let (set_rent_wasm, set_rent_code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - let acl_key = { - let mut s = [0u8; 32]; - s[0] = 1; - s - }; - - // This test can fail due to the encoding changes. In case it becomes too annoying - // let's rewrite so as we use this module controlled call or we serialize it in runtime. - let encoded = hex::encode(Encode::encode(&Call::Contract(super::Call::restore_to( - BOB, - set_rent_code_hash.into(), - ::Balance::from(50u32), - vec![acl_key, acl_key], - )))); - - // `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 automatic 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`", - ); - with_externalities( &mut ExtBuilder::default().existential_deposit(50).build(), diff --git a/substrate/srml/contracts/src/wasm/mod.rs b/substrate/srml/contracts/src/wasm/mod.rs index c832d324b3..2c29b1ead1 100644 --- a/substrate/srml/contracts/src/wasm/mod.rs +++ b/substrate/srml/contracts/src/wasm/mod.rs @@ -185,6 +185,13 @@ mod tests { #[derive(Debug, PartialEq, Eq)] struct DispatchEntry(Call); #[derive(Debug, PartialEq, Eq)] + struct RestoreEntry { + dest: u64, + code_hash: H256, + rent_allowance: u64, + delta: Vec, + } + #[derive(Debug, PartialEq, Eq)] struct CreateEntry { code_hash: H256, endowment: u64, @@ -205,6 +212,7 @@ mod tests { creates: Vec, transfers: Vec, dispatches: Vec, + restores: Vec, // (topics, data) events: Vec<(Vec, Vec)>, next_account_id: u64, @@ -262,6 +270,20 @@ mod tests { fn note_dispatch_call(&mut self, call: Call) { self.dispatches.push(DispatchEntry(call)); } + fn note_restore_to( + &mut self, + dest: u64, + code_hash: H256, + rent_allowance: u64, + delta: Vec, + ) { + self.restores.push(RestoreEntry { + dest, + code_hash, + rent_allowance, + delta, + }); + } fn caller(&self) -> &u64 { &42 } @@ -332,6 +354,20 @@ mod tests { fn note_dispatch_call(&mut self, call: Call) { (**self).note_dispatch_call(call) } + fn note_restore_to( + &mut self, + dest: u64, + code_hash: H256, + rent_allowance: u64, + delta: Vec, + ) { + (**self).note_restore_to( + dest, + code_hash, + rent_allowance, + delta, + ) + } fn caller(&self) -> &u64 { (**self).caller() } diff --git a/substrate/srml/contracts/src/wasm/runtime.rs b/substrate/srml/contracts/src/wasm/runtime.rs index 556aa829cd..fcbc113af9 100644 --- a/substrate/srml/contracts/src/wasm/runtime.rs +++ b/substrate/srml/contracts/src/wasm/runtime.rs @@ -586,6 +586,87 @@ define_env!(Env, , Ok(()) }, + // Record a request to restore the caller contract to the specified contract. + // + // At the finalization stage, i.e. when all changes from the extrinsic that invoked this + // contract are commited, this function will compute a tombstone hash from the caller's + // storage and the given code hash and if the hash matches the hash found in the tombstone at + // the specified address - kill the caller contract and restore the destination contract and set + // the specified `rent_allowance`. All caller's funds are transfered to the destination. + // + // This function doesn't perform restoration right away but defers it to the end of the + // transaction. If there is no tombstone in the destination address or if the hashes don't match + // then restoration is cancelled and no changes are made. + // + // `dest_ptr`, `dest_len` - the pointer and the length of a buffer that encodes `T::AccountId` + // with the address of the to be restored contract. + // `code_hash_ptr`, `code_hash_len` - the pointer and the length of a buffer that encodes + // a code hash of the to be restored contract. + // `rent_allowance_ptr`, `rent_allowance_len` - the pointer and the length of a buffer that + // encodes the rent allowance that must be set in the case of successful restoration. + // `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys + // laid out sequentially. + ext_restore_to( + ctx, + dest_ptr: u32, + dest_len: u32, + code_hash_ptr: u32, + code_hash_len: u32, + rent_allowance_ptr: u32, + rent_allowance_len: u32, + delta_ptr: u32, + delta_count: u32 + ) => { + let dest = { + let dest_buf = read_sandbox_memory(ctx, dest_ptr, dest_len)?; + <::T as system::Trait>::AccountId::decode(&mut &dest_buf[..]) + .ok_or_else(|| sandbox::HostError)? + }; + let code_hash = { + let code_hash_buf = read_sandbox_memory(ctx, code_hash_ptr, code_hash_len)?; + ::T>>::decode(&mut &code_hash_buf[..]) + .ok_or_else(|| sandbox::HostError)? + }; + let rent_allowance = { + let rent_allowance_buf = read_sandbox_memory( + ctx, + rent_allowance_ptr, + rent_allowance_len + )?; + BalanceOf::<::T>::decode(&mut &rent_allowance_buf[..]) + .ok_or_else(|| sandbox::HostError)? + }; + let delta = { + // We don't use `with_capacity` here to not eagerly allocate the user specified amount + // of memory. + let mut delta = Vec::new(); + let mut key_ptr = delta_ptr; + + for _ in 0..delta_count { + const KEY_SIZE: usize = 32; + + // Read the delta into the provided buffer and collect it into the buffer. + let mut delta_key: StorageKey = [0; KEY_SIZE]; + read_sandbox_memory_into_buf(ctx, key_ptr, &mut delta_key)?; + delta.push(delta_key); + + // Offset key_ptr to the next element. + key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or_else(|| sandbox::HostError)?; + } + + delta + }; + + ctx.ext.note_restore_to( + dest, + code_hash, + rent_allowance, + delta, + ); + + Ok(()) + }, + // Returns the size of the scratch buffer. // // For more details on the scratch buffer see `ext_scratch_copy`.