mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 15:21:08 +00:00
srml-contracts: Deferred actions (#3255)
* Switch to deferred actions * Make restore_to a deferred action. * Bump version. * Review fixes.
This commit is contained in:
committed by
Gavin Wood
parent
780942192e
commit
de02aee156
@@ -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,
|
||||
};
|
||||
|
||||
@@ -90,6 +90,15 @@ pub trait Ext {
|
||||
/// Notes a call dispatch.
|
||||
fn note_dispatch_call(&mut self, call: CallOf<Self::T>);
|
||||
|
||||
/// Notes a restoration request.
|
||||
fn note_restore_to(
|
||||
&mut self,
|
||||
dest: AccountIdOf<Self::T>,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
rent_allowance: BalanceOf<Self::T>,
|
||||
delta: Vec<StorageKey>,
|
||||
);
|
||||
|
||||
/// Returns a reference to the account id of the caller.
|
||||
fn caller(&self) -> &AccountIdOf<Self::T>;
|
||||
|
||||
@@ -254,13 +263,41 @@ impl<T: Trait> Token<T> for ExecFeeToken {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(any(feature = "std", test), derive(Debug, PartialEq, Eq, Clone))]
|
||||
pub enum DeferredAction<T: Trait> {
|
||||
DepositEvent {
|
||||
/// A list of topics this event will be deposited with.
|
||||
topics: Vec<T::Hash>,
|
||||
/// The event to deposit.
|
||||
event: Event<T>,
|
||||
},
|
||||
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<T>,
|
||||
/// The initial rent allowance to set.
|
||||
rent_allowance: BalanceOf<T>,
|
||||
/// The keys to delete upon restoration.
|
||||
delta: Vec<StorageKey>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct ExecutionContext<'a, T: Trait + 'a, V, L> {
|
||||
pub self_account: T::AccountId,
|
||||
pub self_trie_id: Option<TrieId>,
|
||||
pub overlay: OverlayAccountDb<'a, T>,
|
||||
pub depth: usize,
|
||||
pub events: Vec<IndexedEvent<T>>,
|
||||
pub calls: Vec<(T::AccountId, T::Call)>,
|
||||
pub deferred: Vec<DeferredAction<T>>,
|
||||
pub config: &'a Config<T>,
|
||||
pub vm: &'a V,
|
||||
pub loader: &'a L,
|
||||
@@ -284,8 +321,7 @@ where
|
||||
self_account: origin,
|
||||
overlay: OverlayAccountDb::<T>::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<Vec<u8>, &'static str>
|
||||
where F: FnOnce(&mut ExecutionContext<T, V, L>) -> Result<Vec<u8>, &'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<T>, L: Loader<T>>(
|
||||
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::T>) {
|
||||
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<Self::T>,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
rent_allowance: BalanceOf<Self::T>,
|
||||
delta: Vec<StorageKey>,
|
||||
) {
|
||||
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<T::Hash>, data: Vec<u8>) {
|
||||
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<DeferredAction<T>> {
|
||||
self.deferred
|
||||
.iter()
|
||||
.filter(|action| match *action {
|
||||
DeferredAction::DepositEvent { .. } => true,
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
struct MockCtx<'a> {
|
||||
ext: &'a mut dyn Ext<T = Test>,
|
||||
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(),
|
||||
},
|
||||
|
||||
@@ -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<T>,
|
||||
rent_allowance: BalanceOf<T>,
|
||||
delta: Vec<exec::StorageKey>
|
||||
) {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
|
||||
.and_then(|c| c.get_alive())
|
||||
.ok_or("Cannot restore from inexisting or tombstone contract")?;
|
||||
|
||||
let current_block = <system::Module<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err("Origin TrieId written in the current block");
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::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::<Vec<_>>();
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::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::<u32>();
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
<ContractInfoOf<T>>::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, <BalanceOf<T>>::zero());
|
||||
T::Currency::deposit_creating(&dest, origin_free_balance);
|
||||
}
|
||||
|
||||
fn on_finalize() {
|
||||
GasSpent::kill();
|
||||
}
|
||||
@@ -727,14 +649,6 @@ impl<T: Trait> Module<T> {
|
||||
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| {
|
||||
<system::Module<T>>::deposit_event_indexed(
|
||||
&*indexed_event.topics,
|
||||
<T as Trait>::Event::from(indexed_event.event).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Refund cost of the unused gas.
|
||||
@@ -743,14 +657,110 @@ impl<T: Trait> Module<T> {
|
||||
// can alter the balance of the caller.
|
||||
gas::refund_unused_gas::<T>(&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,
|
||||
} => <system::Module<T>>::deposit_event_indexed(
|
||||
&*topics,
|
||||
<T as Trait>::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<T>,
|
||||
rent_allowance: BalanceOf<T>,
|
||||
delta: Vec<exec::StorageKey>
|
||||
) -> Result {
|
||||
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
|
||||
.and_then(|c| c.get_alive())
|
||||
.ok_or("Cannot restore from inexisting or tombstone contract")?;
|
||||
|
||||
let current_block = <system::Module<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err("Origin TrieId written in the current block");
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::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::<Vec<_>>();
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::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::<u32>();
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
<ContractInfoOf<T>>::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, <BalanceOf<T>>::zero());
|
||||
T::Currency::deposit_creating(&dest, origin_free_balance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
decl_event! {
|
||||
|
||||
@@ -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::<Test>(CODE_SET_RENT).unwrap();
|
||||
let (restoration_wasm, restoration_code_hash) =
|
||||
compile_module::<Test>(CODE_RESTORATION).unwrap();
|
||||
let (set_rent_wasm, set_rent_code_hash) = compile_module::<Test>(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(),
|
||||
<Test as balances::Trait>::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(),
|
||||
|
||||
@@ -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<StorageKey>,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct CreateEntry {
|
||||
code_hash: H256,
|
||||
endowment: u64,
|
||||
@@ -205,6 +212,7 @@ mod tests {
|
||||
creates: Vec<CreateEntry>,
|
||||
transfers: Vec<TransferEntry>,
|
||||
dispatches: Vec<DispatchEntry>,
|
||||
restores: Vec<RestoreEntry>,
|
||||
// (topics, data)
|
||||
events: Vec<(Vec<H256>, Vec<u8>)>,
|
||||
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<StorageKey>,
|
||||
) {
|
||||
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<StorageKey>,
|
||||
) {
|
||||
(**self).note_restore_to(
|
||||
dest,
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
delta,
|
||||
)
|
||||
}
|
||||
fn caller(&self) -> &u64 {
|
||||
(**self).caller()
|
||||
}
|
||||
|
||||
@@ -586,6 +586,87 @@ define_env!(Env, <E: Ext>,
|
||||
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)?;
|
||||
<<E as Ext>::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)?;
|
||||
<CodeHash<<E as Ext>::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::<<E as Ext>::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`.
|
||||
|
||||
Reference in New Issue
Block a user