mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 15:11:02 +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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user