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:
Yarik Bratashchuk
2022-02-08 13:43:32 +02:00
committed by GitHub
parent 1d62516fad
commit d14e1c641e
10 changed files with 574 additions and 49 deletions
+106 -22
View File
@@ -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,