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
+96 -27
View File
@@ -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 {