mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 15:47:58 +00:00
seal_delegate_call api function (support for library contracts) (#10617)
* seal_call_code implementation - tests - benchmark * Addressing @xgreenx's comments * Fix test-linux-stable-int * Rename seal_call_code to seal_delegate_call * Pass value unchanged into lib contract * Address @athei's comments - whitespace .wat issues - wrong/missing .wat comments - redundant .wat calls/declarations - change order of functions (seal_delegate_call right after seal_call) in decls, tests, benchmark - fix comments, move doc comments to enum variants - remove unnecessary empty lines - rename runtime cost DelegateCall to DelegateCallBase - do not set CallFlags::ALLOW_REENTRY for delegate_call * Do not pass CallFlags::ALLOWS_REENTRY for delegate_call * Update comment for seal_delegate_call and CallFlags * Addressing @athei's comments (minor) * Allow reentry for a new frame after delegate_call (revert) * Same seal_caller and seal_value_transferred for lib contract - test - refactor frame args due to review - logic for seal_caller (please review) * Put caller on frame for delegate_call, minor fixes * Update comment for delegate_call * Addressing @athei's comments * Update weights generated by benchmark * Improve comments * Address @HCastano's comments * Update weights, thanks @joao-paulo-parity * Improve InvalidCallFlags error comment
This commit is contained in:
committed by
GitHub
parent
1d62516fad
commit
d14e1c641e
@@ -1474,6 +1474,58 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
seal_delegate_call {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let hashes = (0..r * API_BENCHMARK_BATCH_SIZE)
|
||||
.map(|i| {
|
||||
let code = WasmModule::<T>::dummy_with_bytes(i);
|
||||
Contracts::<T>::store_code_raw(code.code, whitelisted_caller())?;
|
||||
Ok(code.hash)
|
||||
})
|
||||
.collect::<Result<Vec<_>, &'static str>>()?;
|
||||
let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
|
||||
let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
|
||||
let hashes_len = hashes_bytes.len();
|
||||
let hashes_offset = 0;
|
||||
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "__unstable__",
|
||||
name: "seal_delegate_call",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
ValueType::I32,
|
||||
ValueType::I32,
|
||||
ValueType::I32,
|
||||
ValueType::I32,
|
||||
ValueType::I32,
|
||||
],
|
||||
return_type: Some(ValueType::I32),
|
||||
}],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: hashes_offset as u32,
|
||||
value: hashes_bytes,
|
||||
},
|
||||
],
|
||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||
Regular(Instruction::I32Const(0)), // flags
|
||||
Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr
|
||||
Regular(Instruction::I32Const(0)), // input_data_ptr
|
||||
Regular(Instruction::I32Const(0)), // input_data_len
|
||||
Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr
|
||||
Regular(Instruction::I32Const(0)), // output_len_ptr
|
||||
Regular(Instruction::Call(0)),
|
||||
Regular(Instruction::Drop),
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let callee = instance.addr.clone();
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
seal_call_per_transfer_input_output_kb {
|
||||
let t in 0 .. 1;
|
||||
let i in 0 .. code::max_pages::<T>() * 64;
|
||||
|
||||
@@ -104,6 +104,19 @@ pub trait Ext: sealing::Sealed {
|
||||
allows_reentry: bool,
|
||||
) -> Result<ExecReturnValue, ExecError>;
|
||||
|
||||
/// Execute code in the current frame.
|
||||
///
|
||||
/// Returns the original code size of the called contract.
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<ExecReturnValue, ExecError>
|
||||
fn delegate_call(
|
||||
&mut self,
|
||||
code: CodeHash<Self::T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> Result<ExecReturnValue, ExecError>;
|
||||
|
||||
/// Instantiate a contract from the given code.
|
||||
///
|
||||
/// Returns the original code size of the called contract.
|
||||
@@ -347,6 +360,16 @@ pub struct Frame<T: Config> {
|
||||
nested_storage: storage::meter::NestedMeter<T>,
|
||||
/// If `false` the contract enabled its defense against reentrance attacks.
|
||||
allows_reentry: bool,
|
||||
/// The caller of the currently executing frame which was spawned by `delegate_call`.
|
||||
delegate_caller: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
/// Used in a delegate call frame arguments in order to override the executable and caller.
|
||||
struct DelegatedCall<T: Config, E> {
|
||||
/// The executable which is run instead of the contracts own `executable`.
|
||||
executable: E,
|
||||
/// The account id of the caller contract.
|
||||
caller: T::AccountId,
|
||||
}
|
||||
|
||||
/// Parameter passed in when creating a new `Frame`.
|
||||
@@ -358,6 +381,10 @@ enum FrameArgs<'a, T: Config, E> {
|
||||
dest: T::AccountId,
|
||||
/// If `None` the contract info needs to be reloaded from storage.
|
||||
cached_info: Option<ContractInfo<T>>,
|
||||
/// This frame was created by `seal_delegate_call` and hence uses different code than
|
||||
/// what is stored at [`Self::dest`]. Its caller ([`Frame::delegated_caller`]) is the
|
||||
/// account which called the caller contract
|
||||
delegated_call: Option<DelegatedCall<T, E>>,
|
||||
},
|
||||
Instantiate {
|
||||
/// The contract or signed origin which instantiates the new contract.
|
||||
@@ -513,7 +540,7 @@ where
|
||||
debug_message: Option<&'a mut Vec<u8>>,
|
||||
) -> Result<ExecReturnValue, ExecError> {
|
||||
let (mut stack, executable) = Self::new(
|
||||
FrameArgs::Call { dest, cached_info: None },
|
||||
FrameArgs::Call { dest, cached_info: None, delegated_call: None },
|
||||
origin,
|
||||
gas_meter,
|
||||
storage_meter,
|
||||
@@ -604,33 +631,46 @@ where
|
||||
gas_limit: Weight,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<(Frame<T>, E, Option<u64>), ExecError> {
|
||||
let (account_id, contract_info, executable, entry_point, account_counter) = match frame_args
|
||||
{
|
||||
FrameArgs::Call { dest, cached_info } => {
|
||||
let contract = if let Some(contract) = cached_info {
|
||||
contract
|
||||
} else {
|
||||
<ContractInfoOf<T>>::get(&dest).ok_or(<Error<T>>::ContractNotFound)?
|
||||
};
|
||||
let (account_id, contract_info, executable, delegate_caller, entry_point, account_counter) =
|
||||
match frame_args {
|
||||
FrameArgs::Call { dest, cached_info, delegated_call } => {
|
||||
let contract = if let Some(contract) = cached_info {
|
||||
contract
|
||||
} else {
|
||||
<ContractInfoOf<T>>::get(&dest).ok_or(<Error<T>>::ContractNotFound)?
|
||||
};
|
||||
|
||||
let executable = E::from_storage(contract.code_hash, schedule, gas_meter)?;
|
||||
let (executable, delegate_caller) =
|
||||
if let Some(DelegatedCall { executable, caller }) = delegated_call {
|
||||
(executable, Some(caller))
|
||||
} else {
|
||||
(E::from_storage(contract.code_hash, schedule, gas_meter)?, None)
|
||||
};
|
||||
|
||||
(dest, contract, executable, ExportedFunction::Call, None)
|
||||
},
|
||||
FrameArgs::Instantiate { sender, trie_seed, executable, salt } => {
|
||||
let account_id =
|
||||
<Contracts<T>>::contract_address(&sender, executable.code_hash(), &salt);
|
||||
let trie_id = Storage::<T>::generate_trie_id(&account_id, trie_seed);
|
||||
let contract = Storage::<T>::new_contract(
|
||||
&account_id,
|
||||
trie_id,
|
||||
executable.code_hash().clone(),
|
||||
)?;
|
||||
(account_id, contract, executable, ExportedFunction::Constructor, Some(trie_seed))
|
||||
},
|
||||
};
|
||||
(dest, contract, executable, delegate_caller, ExportedFunction::Call, None)
|
||||
},
|
||||
FrameArgs::Instantiate { sender, trie_seed, executable, salt } => {
|
||||
let account_id =
|
||||
<Contracts<T>>::contract_address(&sender, executable.code_hash(), &salt);
|
||||
let trie_id = Storage::<T>::generate_trie_id(&account_id, trie_seed);
|
||||
let contract = Storage::<T>::new_contract(
|
||||
&account_id,
|
||||
trie_id,
|
||||
executable.code_hash().clone(),
|
||||
)?;
|
||||
(
|
||||
account_id,
|
||||
contract,
|
||||
executable,
|
||||
None,
|
||||
ExportedFunction::Constructor,
|
||||
Some(trie_seed),
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let frame = Frame {
|
||||
delegate_caller,
|
||||
value_transferred,
|
||||
contract_info: CachedContract::Cached(contract_info),
|
||||
account_id,
|
||||
@@ -936,8 +976,11 @@ where
|
||||
CachedContract::Cached(contract) => Some(contract.clone()),
|
||||
_ => None,
|
||||
});
|
||||
let executable =
|
||||
self.push_frame(FrameArgs::Call { dest: to, cached_info }, value, gas_limit)?;
|
||||
let executable = self.push_frame(
|
||||
FrameArgs::Call { dest: to, cached_info, delegated_call: None },
|
||||
value,
|
||||
gas_limit,
|
||||
)?;
|
||||
self.run(executable, input_data)
|
||||
};
|
||||
|
||||
@@ -950,6 +993,28 @@ where
|
||||
result
|
||||
}
|
||||
|
||||
fn delegate_call(
|
||||
&mut self,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> Result<ExecReturnValue, ExecError> {
|
||||
let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?;
|
||||
let top_frame = self.top_frame_mut();
|
||||
let contract_info = top_frame.contract_info().clone();
|
||||
let account_id = top_frame.account_id.clone();
|
||||
let value = top_frame.value_transferred.clone();
|
||||
let executable = self.push_frame(
|
||||
FrameArgs::Call {
|
||||
dest: account_id,
|
||||
cached_info: Some(contract_info),
|
||||
delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }),
|
||||
},
|
||||
value,
|
||||
0,
|
||||
)?;
|
||||
self.run(executable, input_data)
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
gas_limit: Weight,
|
||||
@@ -1030,7 +1095,11 @@ where
|
||||
}
|
||||
|
||||
fn caller(&self) -> &T::AccountId {
|
||||
self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin)
|
||||
if let Some(caller) = &self.top_frame().delegate_caller {
|
||||
&caller
|
||||
} else {
|
||||
self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_contract(&self, address: &T::AccountId) -> bool {
|
||||
|
||||
@@ -570,6 +570,8 @@ pub mod pallet {
|
||||
pub enum Error<T> {
|
||||
/// A new schedule must have a greater version than the current one.
|
||||
InvalidScheduleVersion,
|
||||
/// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`.
|
||||
InvalidCallFlags,
|
||||
/// The executed contract exhausted its gas limit.
|
||||
OutOfGas,
|
||||
/// The output buffer supplied to a contract API call was too small.
|
||||
|
||||
@@ -358,6 +358,9 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_call`.
|
||||
pub call: Weight,
|
||||
|
||||
/// Weight of calling `seal_delegate_call`.
|
||||
pub delegate_call: Weight,
|
||||
|
||||
/// Weight surcharge that is claimed if `seal_call` does a balance transfer.
|
||||
pub call_transfer_surcharge: Weight,
|
||||
|
||||
@@ -615,6 +618,7 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
take_storage_per_byte: cost_byte_batched!(seal_take_storage_per_kb),
|
||||
transfer: cost_batched!(seal_transfer),
|
||||
call: cost_batched!(seal_call),
|
||||
delegate_call: cost_batched!(seal_delegate_call),
|
||||
call_transfer_surcharge: cost_batched_args!(
|
||||
seal_call_per_transfer_input_output_kb,
|
||||
1,
|
||||
|
||||
@@ -692,6 +692,44 @@ fn deploy_and_call_other_contract() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn delegate_call() {
|
||||
let (caller_wasm, caller_code_hash) = compile_module::<Test>("delegate_call").unwrap();
|
||||
let (callee_wasm, callee_code_hash) = compile_module::<Test>("delegate_call_lib").unwrap();
|
||||
let caller_addr = Contracts::contract_address(&ALICE, &caller_code_hash, &[]);
|
||||
|
||||
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
// Instantiate the 'caller'
|
||||
assert_ok!(Contracts::instantiate_with_code(
|
||||
Origin::signed(ALICE),
|
||||
300_000,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
caller_wasm,
|
||||
vec![],
|
||||
vec![],
|
||||
));
|
||||
// Only upload 'callee' code
|
||||
assert_ok!(Contracts::upload_code(
|
||||
Origin::signed(ALICE),
|
||||
callee_wasm,
|
||||
Some(codec::Compact(100_000)),
|
||||
));
|
||||
|
||||
assert_ok!(Contracts::call(
|
||||
Origin::signed(ALICE),
|
||||
caller_addr.clone(),
|
||||
1337,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
callee_code_hash.as_ref().to_vec(),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_self_destruct_through_draning() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("drain").unwrap();
|
||||
|
||||
@@ -302,11 +302,18 @@ mod tests {
|
||||
allows_reentry: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct CallCodeEntry {
|
||||
code_hash: H256,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct MockExt {
|
||||
storage: HashMap<StorageKey, Vec<u8>>,
|
||||
instantiates: Vec<InstantiateEntry>,
|
||||
terminations: Vec<TerminationEntry>,
|
||||
calls: Vec<CallEntry>,
|
||||
code_calls: Vec<CallCodeEntry>,
|
||||
transfers: Vec<TransferEntry>,
|
||||
// (topics, data)
|
||||
events: Vec<(Vec<H256>, Vec<u8>)>,
|
||||
@@ -329,6 +336,7 @@ mod tests {
|
||||
instantiates: Default::default(),
|
||||
terminations: Default::default(),
|
||||
calls: Default::default(),
|
||||
code_calls: Default::default(),
|
||||
transfers: Default::default(),
|
||||
events: Default::default(),
|
||||
runtime_calls: Default::default(),
|
||||
@@ -354,6 +362,14 @@ mod tests {
|
||||
self.calls.push(CallEntry { to, value, data, allows_reentry });
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() })
|
||||
}
|
||||
fn delegate_call(
|
||||
&mut self,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
data: Vec<u8>,
|
||||
) -> Result<ExecReturnValue, ExecError> {
|
||||
self.code_calls.push(CallCodeEntry { code_hash, data });
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() })
|
||||
}
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
gas_limit: Weight,
|
||||
@@ -579,6 +595,53 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn contract_delegate_call() {
|
||||
const CODE: &str = r#"
|
||||
(module
|
||||
;; seal_delegate_call(
|
||||
;; flags: u32,
|
||||
;; code_hash_ptr: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32,
|
||||
;; output_ptr: u32,
|
||||
;; output_len_ptr: u32
|
||||
;;) -> u32
|
||||
(import "__unstable__" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $seal_delegate_call
|
||||
(i32.const 0) ;; No flags are set
|
||||
(i32.const 4) ;; Pointer to "callee" code_hash.
|
||||
(i32.const 36) ;; Pointer to input data buffer address
|
||||
(i32.const 4) ;; Length of input data buffer
|
||||
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
|
||||
(i32.const 0) ;; Length is ignored in this case
|
||||
)
|
||||
)
|
||||
)
|
||||
(func (export "deploy"))
|
||||
|
||||
;; Callee code_hash
|
||||
(data (i32.const 4)
|
||||
"\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"
|
||||
)
|
||||
|
||||
(data (i32.const 36) "\01\02\03\04")
|
||||
)
|
||||
"#;
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(CODE, vec![], &mut mock_ext));
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.code_calls,
|
||||
&[CallCodeEntry { code_hash: [0x11; 32].into(), data: vec![1, 2, 3, 4] }]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_call_forward_input() {
|
||||
const CODE: &str = r#"
|
||||
|
||||
@@ -190,6 +190,9 @@ pub enum RuntimeCosts {
|
||||
Transfer,
|
||||
/// Weight of calling `seal_call` for the given input size.
|
||||
CallBase(u32),
|
||||
/// Weight of calling `seal_delegate_call` for the given input size.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
DelegateCallBase(u32),
|
||||
/// Weight of the transfer performed during a call.
|
||||
CallSurchargeTransfer,
|
||||
/// Weight of output received through `seal_call` for the given size.
|
||||
@@ -275,6 +278,9 @@ impl RuntimeCosts {
|
||||
s.call.saturating_add(s.call_per_input_byte.saturating_mul(len.into())),
|
||||
CallSurchargeTransfer => s.call_transfer_surcharge,
|
||||
CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()),
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
DelegateCallBase(len) =>
|
||||
s.delegate_call.saturating_add(s.call_per_input_byte.saturating_mul(len.into())),
|
||||
InstantiateBase { input_data_len, salt_len } => s
|
||||
.instantiate
|
||||
.saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into()))
|
||||
@@ -327,7 +333,7 @@ where
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags used to change the behaviour of `seal_call`.
|
||||
/// Flags used to change the behaviour of `seal_call` and `seal_delegate_call`.
|
||||
struct CallFlags: u32 {
|
||||
/// Forward the input of current function to the callee.
|
||||
///
|
||||
@@ -363,10 +369,34 @@ bitflags! {
|
||||
/// Without this flag any reentrancy into the current contract that originates from
|
||||
/// the callee (or any of its callees) is denied. This includes the first callee:
|
||||
/// You cannot call into yourself with this flag set.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// For `seal_delegate_call` should be always unset, otherwise
|
||||
/// [`Error::InvalidCallFlags`] is returned.
|
||||
const ALLOW_REENTRY = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of call that should be performed.
|
||||
enum CallType {
|
||||
/// Execute another instantiated contract
|
||||
Call { callee_ptr: u32, value_ptr: u32, gas: u64 },
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
/// Execute deployed code in the context (storage, account ID, value) of the caller contract
|
||||
DelegateCall { code_hash_ptr: u32 },
|
||||
}
|
||||
|
||||
impl CallType {
|
||||
fn cost(&self, input_data_len: u32) -> RuntimeCosts {
|
||||
match self {
|
||||
CallType::Call { .. } => RuntimeCosts::CallBase(input_data_len),
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase(input_data_len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is only appropriate when writing out data of constant size that does not depend on user
|
||||
/// input. In this case the costs for this copy was already charged as part of the token at
|
||||
/// the beginning of the API entry point.
|
||||
@@ -411,7 +441,7 @@ where
|
||||
// The trap was the result of the execution `return` host function.
|
||||
TrapReason::Return(ReturnData { flags, data }) => {
|
||||
let flags = ReturnFlags::from_bits(flags)
|
||||
.ok_or_else(|| "used reserved bit in return flags")?;
|
||||
.ok_or_else(|| Error::<E::T>::InvalidCallFlags)?;
|
||||
Ok(ExecReturnValue { flags, data: Bytes(data) })
|
||||
},
|
||||
TrapReason::Termination =>
|
||||
@@ -693,18 +723,13 @@ where
|
||||
fn call(
|
||||
&mut self,
|
||||
flags: CallFlags,
|
||||
callee_ptr: u32,
|
||||
gas: u64,
|
||||
value_ptr: u32,
|
||||
call_type: CallType,
|
||||
input_data_ptr: u32,
|
||||
input_data_len: u32,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32,
|
||||
) -> Result<ReturnCode, TrapReason> {
|
||||
self.charge_gas(RuntimeCosts::CallBase(input_data_len))?;
|
||||
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
|
||||
self.read_sandbox_memory_as(callee_ptr)?;
|
||||
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
|
||||
self.charge_gas(call_type.cost(input_data_len))?;
|
||||
let input_data = if flags.contains(CallFlags::CLONE_INPUT) {
|
||||
self.input_data.as_ref().ok_or_else(|| Error::<E::T>::InputForwarded)?.clone()
|
||||
} else if flags.contains(CallFlags::FORWARD_INPUT) {
|
||||
@@ -712,12 +737,32 @@ where
|
||||
} else {
|
||||
self.read_sandbox_memory(input_data_ptr, input_data_len)?
|
||||
};
|
||||
if value > 0u32.into() {
|
||||
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
||||
}
|
||||
let ext = &mut self.ext;
|
||||
let call_outcome =
|
||||
ext.call(gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY));
|
||||
|
||||
let call_outcome = match call_type {
|
||||
CallType::Call { callee_ptr, value_ptr, gas } => {
|
||||
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
|
||||
self.read_sandbox_memory_as(callee_ptr)?;
|
||||
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
|
||||
if value > 0u32.into() {
|
||||
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
||||
}
|
||||
self.ext.call(
|
||||
gas,
|
||||
callee,
|
||||
value,
|
||||
input_data,
|
||||
flags.contains(CallFlags::ALLOW_REENTRY),
|
||||
)
|
||||
},
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallType::DelegateCall { code_hash_ptr } => {
|
||||
if flags.contains(CallFlags::ALLOW_REENTRY) {
|
||||
return Err(Error::<E::T>::InvalidCallFlags.into())
|
||||
}
|
||||
let code_hash = self.read_sandbox_memory_as(code_hash_ptr)?;
|
||||
self.ext.delegate_call(code_hash, input_data)
|
||||
},
|
||||
};
|
||||
|
||||
// `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to
|
||||
// a halt anyways without anymore code being executed.
|
||||
@@ -990,9 +1035,7 @@ define_env!(Env, <E: Ext>,
|
||||
) -> ReturnCode => {
|
||||
ctx.call(
|
||||
CallFlags::ALLOW_REENTRY,
|
||||
callee_ptr,
|
||||
gas,
|
||||
value_ptr,
|
||||
CallType::Call{callee_ptr, value_ptr, gas},
|
||||
input_data_ptr,
|
||||
input_data_len,
|
||||
output_ptr,
|
||||
@@ -1041,10 +1084,51 @@ define_env!(Env, <E: Ext>,
|
||||
output_len_ptr: u32
|
||||
) -> ReturnCode => {
|
||||
ctx.call(
|
||||
CallFlags::from_bits(flags).ok_or_else(|| "used reserved bit in CallFlags")?,
|
||||
callee_ptr,
|
||||
gas,
|
||||
value_ptr,
|
||||
CallFlags::from_bits(flags).ok_or_else(|| Error::<E::T>::InvalidCallFlags)?,
|
||||
CallType::Call{callee_ptr, value_ptr, gas},
|
||||
input_data_ptr,
|
||||
input_data_len,
|
||||
output_ptr,
|
||||
output_len_ptr,
|
||||
)
|
||||
},
|
||||
|
||||
// Execute code in the context (storage, caller, value) of the current contract.
|
||||
//
|
||||
// Reentrancy protection is always disabled since the callee is allowed
|
||||
// to modify the callers storage. This makes going through a reentrancy attack
|
||||
// unnecessary for the callee when it wants to exploit the caller.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - flags: See [`CallFlags`] for a documentation of the supported flags.
|
||||
// - code_hash: a pointer to the hash of the code to be called.
|
||||
// - input_data_ptr: a pointer to a buffer to be used as input data to the callee.
|
||||
// - input_data_len: length of the input data buffer.
|
||||
// - output_ptr: a pointer where the output buffer is copied to.
|
||||
// - output_len_ptr: in-out pointer to where the length of the buffer is read from
|
||||
// and the actual length is written to.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// An error means that the call wasn't successful and no output buffer is returned unless
|
||||
// stated otherwise.
|
||||
//
|
||||
// `ReturnCode::CalleeReverted`: Output buffer is returned.
|
||||
// `ReturnCode::CalleeTrapped`
|
||||
// `ReturnCode::CodeNotFound`
|
||||
[__unstable__] seal_delegate_call(
|
||||
ctx,
|
||||
flags: u32,
|
||||
code_hash_ptr: u32,
|
||||
input_data_ptr: u32,
|
||||
input_data_len: u32,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32
|
||||
) -> ReturnCode => {
|
||||
ctx.call(
|
||||
CallFlags::from_bits(flags).ok_or_else(|| Error::<E::T>::InvalidCallFlags)?,
|
||||
CallType::DelegateCall{code_hash_ptr},
|
||||
input_data_ptr,
|
||||
input_data_len,
|
||||
output_ptr,
|
||||
|
||||
@@ -88,6 +88,7 @@ pub trait WeightInfo {
|
||||
fn seal_take_storage_per_kb(n: u32, ) -> Weight;
|
||||
fn seal_transfer(r: u32, ) -> Weight;
|
||||
fn seal_call(r: u32, ) -> Weight;
|
||||
fn seal_delegate_call(r: u32, ) -> Weight;
|
||||
fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight;
|
||||
fn seal_instantiate(r: u32, ) -> Weight;
|
||||
fn seal_instantiate_per_input_output_salt_kb(i: u32, o: u32, s: u32, ) -> Weight;
|
||||
@@ -618,6 +619,17 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
.saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight)))
|
||||
}
|
||||
// Storage: System Account (r:1 w:0)
|
||||
// Storage: Contracts ContractInfoOf (r:1 w:1)
|
||||
// Storage: Contracts CodeStorage (r:1 w:0)
|
||||
// Storage: Timestamp Now (r:1 w:0)
|
||||
fn seal_delegate_call(r: u32, ) -> Weight {
|
||||
(0 as Weight)
|
||||
// Standard Error: 11_788_000
|
||||
.saturating_add((19_855_594_000 as Weight).saturating_mul(r as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads((99 as Weight).saturating_mul(r as Weight)))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: System Account (r:1 w:0)
|
||||
// Storage: Contracts ContractInfoOf (r:101 w:101)
|
||||
// Storage: Contracts CodeStorage (r:2 w:0)
|
||||
// Storage: Timestamp Now (r:1 w:0)
|
||||
@@ -1486,6 +1498,17 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight)))
|
||||
}
|
||||
// Storage: System Account (r:1 w:0)
|
||||
// Storage: Contracts ContractInfoOf (r:1 w:1)
|
||||
// Storage: Contracts CodeStorage (r:1 w:0)
|
||||
// Storage: Timestamp Now (r:1 w:0)
|
||||
fn seal_delegate_call(r: u32, ) -> Weight {
|
||||
(0 as Weight)
|
||||
// Standard Error: 11_788_000
|
||||
.saturating_add((19_855_594_000 as Weight).saturating_mul(r as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads((99 as Weight).saturating_mul(r as Weight)))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
// Storage: System Account (r:1 w:0)
|
||||
// Storage: Contracts ContractInfoOf (r:101 w:101)
|
||||
// Storage: Contracts CodeStorage (r:2 w:0)
|
||||
// Storage: Timestamp Now (r:1 w:0)
|
||||
|
||||
Reference in New Issue
Block a user