mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 10:31:03 +00:00
contracts: Add new seal_call that offers new features (#8909)
* Add new `seal_call` that offers new features * Fix doc typo Co-authored-by: Michael Müller <michi@parity.io> * Fix doc typos Co-authored-by: Michael Müller <michi@parity.io> * Fix comment on assert * Update CHANGELOG.md Co-authored-by: Michael Müller <michi@parity.io>
This commit is contained in:
committed by
GitHub
parent
5c14dd3f32
commit
60256d752e
Generated
+1
@@ -4838,6 +4838,7 @@ name = "pallet-contracts"
|
|||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
|
"bitflags",
|
||||||
"frame-benchmarking",
|
"frame-benchmarking",
|
||||||
"frame-support",
|
"frame-support",
|
||||||
"frame-system",
|
"frame-system",
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ In other words: Upgrading this pallet will not break pre-existing contracts.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- New **unstable** version of `seal_call` that offers more features.
|
||||||
|
[#8909](https://github.com/paritytech/substrate/pull/8909)
|
||||||
|
|
||||||
- New **unstable** `seal_rent_params` and `seal_rent_status` contract callable function.
|
- New **unstable** `seal_rent_params` and `seal_rent_status` contract callable function.
|
||||||
[#8231](https://github.com/paritytech/substrate/pull/8231)
|
[#8231](https://github.com/paritytech/substrate/pull/8231)
|
||||||
[#8780](https://github.com/paritytech/substrate/pull/8780)
|
[#8780](https://github.com/paritytech/substrate/pull/8780)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ readme = "README.md"
|
|||||||
targets = ["x86_64-unknown-linux-gnu"]
|
targets = ["x86_64-unknown-linux-gnu"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitflags = "1.0"
|
||||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
|
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
pwasm-utils = { version = "0.18", default-features = false }
|
pwasm-utils = { version = "0.18", default-features = false }
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ pub trait Ext: sealing::Sealed {
|
|||||||
to: AccountIdOf<Self::T>,
|
to: AccountIdOf<Self::T>,
|
||||||
value: BalanceOf<Self::T>,
|
value: BalanceOf<Self::T>,
|
||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
|
allows_reentry: bool,
|
||||||
) -> Result<(ExecReturnValue, u32), (ExecError, u32)>;
|
) -> Result<(ExecReturnValue, u32), (ExecError, u32)>;
|
||||||
|
|
||||||
/// Instantiate a contract from the given code.
|
/// Instantiate a contract from the given code.
|
||||||
@@ -457,6 +458,8 @@ pub struct Frame<T: Config> {
|
|||||||
entry_point: ExportedFunction,
|
entry_point: ExportedFunction,
|
||||||
/// The gas meter capped to the supplied gas limit.
|
/// The gas meter capped to the supplied gas limit.
|
||||||
nested_meter: GasMeter<T>,
|
nested_meter: GasMeter<T>,
|
||||||
|
/// If `false` the contract enabled its defense against reentrance attacks.
|
||||||
|
allows_reentry: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameter passed in when creating a new `Frame`.
|
/// Parameter passed in when creating a new `Frame`.
|
||||||
@@ -731,6 +734,7 @@ where
|
|||||||
entry_point,
|
entry_point,
|
||||||
nested_meter: gas_meter.nested(gas_limit)
|
nested_meter: gas_meter.nested(gas_limit)
|
||||||
.map_err(|e| (e.into(), executable.code_len()))?,
|
.map_err(|e| (e.into(), executable.code_len()))?,
|
||||||
|
allows_reentry: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((frame, executable))
|
Ok((frame, executable))
|
||||||
@@ -1014,6 +1018,11 @@ where
|
|||||||
self.frames().skip(1).any(|f| &f.account_id == account_id)
|
self.frames().skip(1).any(|f| &f.account_id == account_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the specified contract allows to be reentered right now.
|
||||||
|
fn allows_reentry(&self, id: &AccountIdOf<T>) -> bool {
|
||||||
|
!self.frames().any(|f| &f.account_id == id && !f.allows_reentry)
|
||||||
|
}
|
||||||
|
|
||||||
/// Increments the cached account id and returns the value to be used for the trie_id.
|
/// Increments the cached account id and returns the value to be used for the trie_id.
|
||||||
fn next_trie_seed(&mut self) -> u64 {
|
fn next_trie_seed(&mut self) -> u64 {
|
||||||
let next = if let Some(current) = self.account_counter {
|
let next = if let Some(current) = self.account_counter {
|
||||||
@@ -1045,25 +1054,44 @@ where
|
|||||||
to: T::AccountId,
|
to: T::AccountId,
|
||||||
value: BalanceOf<T>,
|
value: BalanceOf<T>,
|
||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
|
allows_reentry: bool,
|
||||||
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
|
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
|
||||||
// We ignore instantiate frames in our search for a cached contract.
|
// Before pushing the new frame: Protect the caller contract against reentrancy attacks.
|
||||||
// Otherwise it would be possible to recursively call a contract from its own
|
// It is important to do this before calling `allows_reentry` so that a direct recursion
|
||||||
// constructor: We disallow calling not fully constructed contracts.
|
// is caught by it.
|
||||||
let cached_info = self
|
self.top_frame_mut().allows_reentry = allows_reentry;
|
||||||
.frames()
|
|
||||||
.find(|f| f.entry_point == ExportedFunction::Call && f.account_id == to)
|
let try_call = || {
|
||||||
.and_then(|f| {
|
if !self.allows_reentry(&to) {
|
||||||
match &f.contract_info {
|
return Err((<Error<T>>::ReentranceDenied.into(), 0));
|
||||||
CachedContract::Cached(contract) => Some(contract.clone()),
|
}
|
||||||
_ => None,
|
// We ignore instantiate frames in our search for a cached contract.
|
||||||
}
|
// Otherwise it would be possible to recursively call a contract from its own
|
||||||
});
|
// constructor: We disallow calling not fully constructed contracts.
|
||||||
let executable = self.push_frame(
|
let cached_info = self
|
||||||
FrameArgs::Call{dest: to, cached_info},
|
.frames()
|
||||||
value,
|
.find(|f| f.entry_point == ExportedFunction::Call && f.account_id == to)
|
||||||
gas_limit
|
.and_then(|f| {
|
||||||
)?;
|
match &f.contract_info {
|
||||||
self.run(executable, input_data)
|
CachedContract::Cached(contract) => Some(contract.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let executable = self.push_frame(
|
||||||
|
FrameArgs::Call{dest: to, cached_info},
|
||||||
|
value,
|
||||||
|
gas_limit
|
||||||
|
)?;
|
||||||
|
self.run(executable, input_data)
|
||||||
|
};
|
||||||
|
|
||||||
|
// We need to make sure to reset `allows_reentry` even on failure.
|
||||||
|
let result = try_call();
|
||||||
|
|
||||||
|
// Protection is on a per call basis.
|
||||||
|
self.top_frame_mut().allows_reentry = true;
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate(
|
fn instantiate(
|
||||||
@@ -1097,7 +1125,7 @@ where
|
|||||||
beneficiary: &AccountIdOf<Self::T>,
|
beneficiary: &AccountIdOf<Self::T>,
|
||||||
) -> Result<u32, (DispatchError, u32)> {
|
) -> Result<u32, (DispatchError, u32)> {
|
||||||
if self.is_recursive() {
|
if self.is_recursive() {
|
||||||
return Err((Error::<T>::ReentranceDenied.into(), 0));
|
return Err((Error::<T>::TerminatedWhileReentrant.into(), 0));
|
||||||
}
|
}
|
||||||
let frame = self.top_frame_mut();
|
let frame = self.top_frame_mut();
|
||||||
let info = frame.terminate();
|
let info = frame.terminate();
|
||||||
@@ -1125,7 +1153,7 @@ where
|
|||||||
delta: Vec<StorageKey>,
|
delta: Vec<StorageKey>,
|
||||||
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
|
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
|
||||||
if self.is_recursive() {
|
if self.is_recursive() {
|
||||||
return Err((Error::<T>::ReentranceDenied.into(), 0, 0));
|
return Err((Error::<T>::TerminatedWhileReentrant.into(), 0, 0));
|
||||||
}
|
}
|
||||||
let origin_contract = self.top_frame_mut().contract_info().clone();
|
let origin_contract = self.top_frame_mut().contract_info().clone();
|
||||||
let result = Rent::<T, E>::restore_to(
|
let result = Rent::<T, E>::restore_to(
|
||||||
@@ -1308,12 +1336,14 @@ mod tests {
|
|||||||
exec::ExportedFunction::*,
|
exec::ExportedFunction::*,
|
||||||
Error, Weight,
|
Error, Weight,
|
||||||
};
|
};
|
||||||
|
use codec::{Encode, Decode};
|
||||||
use sp_core::Bytes;
|
use sp_core::Bytes;
|
||||||
use sp_runtime::DispatchError;
|
use sp_runtime::DispatchError;
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
use pretty_assertions::{assert_eq, assert_ne};
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
use pallet_contracts_primitives::ReturnFlags;
|
use pallet_contracts_primitives::ReturnFlags;
|
||||||
|
use frame_support::{assert_ok, assert_err};
|
||||||
|
|
||||||
type MockStack<'a> = Stack<'a, Test, MockExecutable>;
|
type MockStack<'a> = Stack<'a, Test, MockExecutable>;
|
||||||
|
|
||||||
@@ -1731,7 +1761,7 @@ mod tests {
|
|||||||
let value = Default::default();
|
let value = Default::default();
|
||||||
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
|
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
|
||||||
// Try to call into yourself.
|
// Try to call into yourself.
|
||||||
let r = ctx.ext.call(0, BOB, 0, vec![]);
|
let r = ctx.ext.call(0, BOB, 0, vec![], true);
|
||||||
|
|
||||||
REACHED_BOTTOM.with(|reached_bottom| {
|
REACHED_BOTTOM.with(|reached_bottom| {
|
||||||
let mut reached_bottom = reached_bottom.borrow_mut();
|
let mut reached_bottom = reached_bottom.borrow_mut();
|
||||||
@@ -1789,7 +1819,7 @@ mod tests {
|
|||||||
|
|
||||||
// Call into CHARLIE contract.
|
// Call into CHARLIE contract.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
ctx.ext.call(0, CHARLIE, 0, vec![]),
|
ctx.ext.call(0, CHARLIE, 0, vec![], true),
|
||||||
Ok(_)
|
Ok(_)
|
||||||
);
|
);
|
||||||
exec_success()
|
exec_success()
|
||||||
@@ -1832,7 +1862,7 @@ mod tests {
|
|||||||
|
|
||||||
// Call into charlie contract.
|
// Call into charlie contract.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
ctx.ext.call(0, CHARLIE, 0, vec![]),
|
ctx.ext.call(0, CHARLIE, 0, vec![], true),
|
||||||
Ok(_)
|
Ok(_)
|
||||||
);
|
);
|
||||||
exec_success()
|
exec_success()
|
||||||
@@ -2263,7 +2293,7 @@ mod tests {
|
|||||||
assert_ne!(original_allowance, changed_allowance);
|
assert_ne!(original_allowance, changed_allowance);
|
||||||
ctx.ext.set_rent_allowance(changed_allowance);
|
ctx.ext.set_rent_allowance(changed_allowance);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.ext.call(0, CHARLIE, 0, vec![]).map(|v| v.0).map_err(|e| e.0),
|
ctx.ext.call(0, CHARLIE, 0, vec![], true).map(|v| v.0).map_err(|e| e.0),
|
||||||
exec_trapped()
|
exec_trapped()
|
||||||
);
|
);
|
||||||
assert_eq!(ctx.ext.rent_allowance(), changed_allowance);
|
assert_eq!(ctx.ext.rent_allowance(), changed_allowance);
|
||||||
@@ -2272,7 +2302,7 @@ mod tests {
|
|||||||
exec_success()
|
exec_success()
|
||||||
});
|
});
|
||||||
let code_charlie = MockLoader::insert(Call, |ctx, _| {
|
let code_charlie = MockLoader::insert(Call, |ctx, _| {
|
||||||
assert!(ctx.ext.call(0, BOB, 0, vec![99]).is_ok());
|
assert!(ctx.ext.call(0, BOB, 0, vec![99], true).is_ok());
|
||||||
exec_trapped()
|
exec_trapped()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2299,7 +2329,7 @@ mod tests {
|
|||||||
fn recursive_call_during_constructor_fails() {
|
fn recursive_call_during_constructor_fails() {
|
||||||
let code = MockLoader::insert(Constructor, |ctx, _| {
|
let code = MockLoader::insert(Constructor, |ctx, _| {
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![]),
|
ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![], true),
|
||||||
Err((ExecError{error, ..}, _)) if error == <Error<Test>>::ContractNotFound.into()
|
Err((ExecError{error, ..}, _)) if error == <Error<Test>>::ContractNotFound.into()
|
||||||
);
|
);
|
||||||
exec_success()
|
exec_success()
|
||||||
@@ -2390,4 +2420,84 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text");
|
assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_reentry_direct_recursion() {
|
||||||
|
// call the contract passed as input with disabled reentry
|
||||||
|
let code_bob = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap();
|
||||||
|
ctx.ext.call(0, dest, 0, vec![], false).map(|v| v.0).map_err(|e| e.0)
|
||||||
|
});
|
||||||
|
|
||||||
|
let code_charlie = MockLoader::insert(Call, |_, _| {
|
||||||
|
exec_success()
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtBuilder::default().build().execute_with(|| {
|
||||||
|
let schedule = <Test as Config>::Schedule::get();
|
||||||
|
place_contract(&BOB, code_bob);
|
||||||
|
place_contract(&CHARLIE, code_charlie);
|
||||||
|
|
||||||
|
// Calling another contract should succeed
|
||||||
|
assert_ok!(MockStack::run_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||||
|
&schedule,
|
||||||
|
0,
|
||||||
|
CHARLIE.encode(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Calling into oneself fails
|
||||||
|
assert_err!(
|
||||||
|
MockStack::run_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||||
|
&schedule,
|
||||||
|
0,
|
||||||
|
BOB.encode(),
|
||||||
|
None,
|
||||||
|
).map_err(|e| e.0.error),
|
||||||
|
<Error<Test>>::ReentranceDenied,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_deny_reentry() {
|
||||||
|
let code_bob = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
if ctx.input_data[0] == 0 {
|
||||||
|
ctx.ext.call(0, CHARLIE, 0, vec![], false).map(|v| v.0).map_err(|e| e.0)
|
||||||
|
} else {
|
||||||
|
exec_success()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// call BOB with input set to '1'
|
||||||
|
let code_charlie = MockLoader::insert(Call, |ctx, _| {
|
||||||
|
ctx.ext.call(0, BOB, 0, vec![1], true).map(|v| v.0).map_err(|e| e.0)
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtBuilder::default().build().execute_with(|| {
|
||||||
|
let schedule = <Test as Config>::Schedule::get();
|
||||||
|
place_contract(&BOB, code_bob);
|
||||||
|
place_contract(&CHARLIE, code_charlie);
|
||||||
|
|
||||||
|
// BOB -> CHARLIE -> BOB fails as BOB denies reentry.
|
||||||
|
assert_err!(
|
||||||
|
MockStack::run_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||||
|
&schedule,
|
||||||
|
0,
|
||||||
|
vec![0],
|
||||||
|
None,
|
||||||
|
).map_err(|e| e.0.error),
|
||||||
|
<Error<Test>>::ReentranceDenied,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -562,12 +562,11 @@ pub mod pallet {
|
|||||||
ContractTrapped,
|
ContractTrapped,
|
||||||
/// The size defined in `T::MaxValueSize` was exceeded.
|
/// The size defined in `T::MaxValueSize` was exceeded.
|
||||||
ValueTooLarge,
|
ValueTooLarge,
|
||||||
/// The action performed is not allowed while the contract performing it is already
|
/// Termination of a contract is not allowed while the contract is already
|
||||||
/// on the call stack. Those actions are contract self destruction and restoration
|
/// on the call stack. Can be triggered by `seal_terminate` or `seal_restore_to.
|
||||||
/// of a tombstone.
|
TerminatedWhileReentrant,
|
||||||
ReentranceDenied,
|
/// `seal_call` forwarded this contracts input. It therefore is no longer available.
|
||||||
/// `seal_input` was called twice from the same contract execution context.
|
InputForwarded,
|
||||||
InputAlreadyRead,
|
|
||||||
/// The subject passed to `seal_random` exceeds the limit.
|
/// The subject passed to `seal_random` exceeds the limit.
|
||||||
RandomSubjectTooLong,
|
RandomSubjectTooLong,
|
||||||
/// The amount of topics passed to `seal_deposit_events` exceeds the limit.
|
/// The amount of topics passed to `seal_deposit_events` exceeds the limit.
|
||||||
@@ -602,6 +601,8 @@ pub mod pallet {
|
|||||||
TerminatedInConstructor,
|
TerminatedInConstructor,
|
||||||
/// The debug message specified to `seal_debug_message` does contain invalid UTF-8.
|
/// The debug message specified to `seal_debug_message` does contain invalid UTF-8.
|
||||||
DebugMessageInvalidUTF8,
|
DebugMessageInvalidUTF8,
|
||||||
|
/// A call tried to invoke a contract that is flagged as non-reentrant.
|
||||||
|
ReentranceDenied,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
||||||
|
|||||||
@@ -289,7 +289,14 @@ mod tests {
|
|||||||
struct TransferEntry {
|
struct TransferEntry {
|
||||||
to: AccountIdOf<Test>,
|
to: AccountIdOf<Test>,
|
||||||
value: u64,
|
value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
struct CallEntry {
|
||||||
|
to: AccountIdOf<Test>,
|
||||||
|
value: u64,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
allows_reentry: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MockExt {
|
pub struct MockExt {
|
||||||
@@ -297,6 +304,7 @@ mod tests {
|
|||||||
rent_allowance: u64,
|
rent_allowance: u64,
|
||||||
instantiates: Vec<InstantiateEntry>,
|
instantiates: Vec<InstantiateEntry>,
|
||||||
terminations: Vec<TerminationEntry>,
|
terminations: Vec<TerminationEntry>,
|
||||||
|
calls: Vec<CallEntry>,
|
||||||
transfers: Vec<TransferEntry>,
|
transfers: Vec<TransferEntry>,
|
||||||
restores: Vec<RestoreEntry>,
|
restores: Vec<RestoreEntry>,
|
||||||
// (topics, data)
|
// (topics, data)
|
||||||
@@ -307,6 +315,11 @@ mod tests {
|
|||||||
debug_buffer: Vec<u8>,
|
debug_buffer: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The call is mocked and just returns this hardcoded value.
|
||||||
|
fn call_return_data() -> Bytes {
|
||||||
|
Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for MockExt {
|
impl Default for MockExt {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -314,6 +327,7 @@ mod tests {
|
|||||||
rent_allowance: Default::default(),
|
rent_allowance: Default::default(),
|
||||||
instantiates: Default::default(),
|
instantiates: Default::default(),
|
||||||
terminations: Default::default(),
|
terminations: Default::default(),
|
||||||
|
calls: Default::default(),
|
||||||
transfers: Default::default(),
|
transfers: Default::default(),
|
||||||
restores: Default::default(),
|
restores: Default::default(),
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
@@ -334,13 +348,15 @@ mod tests {
|
|||||||
to: AccountIdOf<Self::T>,
|
to: AccountIdOf<Self::T>,
|
||||||
value: u64,
|
value: u64,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
allows_reentry: bool,
|
||||||
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
|
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
|
||||||
self.transfers.push(TransferEntry {
|
self.calls.push(CallEntry {
|
||||||
to,
|
to,
|
||||||
value,
|
value,
|
||||||
data: data,
|
data,
|
||||||
|
allows_reentry,
|
||||||
});
|
});
|
||||||
Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }, 0))
|
Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() }, 0))
|
||||||
}
|
}
|
||||||
fn instantiate(
|
fn instantiate(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -374,7 +390,6 @@ mod tests {
|
|||||||
self.transfers.push(TransferEntry {
|
self.transfers.push(TransferEntry {
|
||||||
to: to.clone(),
|
to: to.clone(),
|
||||||
value,
|
value,
|
||||||
data: Vec::new(),
|
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -526,7 +541,6 @@ mod tests {
|
|||||||
&[TransferEntry {
|
&[TransferEntry {
|
||||||
to: ALICE,
|
to: ALICE,
|
||||||
value: 153,
|
value: 153,
|
||||||
data: Vec::new(),
|
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -587,11 +601,192 @@ mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&mock_ext.transfers,
|
&mock_ext.calls,
|
||||||
&[TransferEntry {
|
&[CallEntry {
|
||||||
to: ALICE,
|
to: ALICE,
|
||||||
value: 6,
|
value: 6,
|
||||||
data: vec![1, 2, 3, 4],
|
data: vec![1, 2, 3, 4],
|
||||||
|
allows_reentry: true,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-interface")]
|
||||||
|
fn contract_call_forward_input() {
|
||||||
|
const CODE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
(func (export "call")
|
||||||
|
(drop
|
||||||
|
(call $seal_call
|
||||||
|
(i32.const 1) ;; Set FORWARD_INPUT bit
|
||||||
|
(i32.const 4) ;; Pointer to "callee" address.
|
||||||
|
(i32.const 32) ;; Length of "callee" address.
|
||||||
|
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||||
|
(i32.const 36) ;; Pointer to the buffer with value to transfer
|
||||||
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
|
(i32.const 44) ;; 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; triggers a trap because we already forwarded the input
|
||||||
|
(call $seal_input (i32.const 1) (i32.const 44))
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
;; Destination AccountId (ALICE)
|
||||||
|
(data (i32.const 4)
|
||||||
|
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||||
|
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Amount of value to transfer.
|
||||||
|
;; Represented by u64 (8 bytes long) in little endian.
|
||||||
|
(data (i32.const 36) "\2A\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; The input is ignored because we forward our own input
|
||||||
|
(data (i32.const 44) "\01\02\03\04")
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
let mut mock_ext = MockExt::default();
|
||||||
|
let input = vec![0xff, 0x2a, 0x99, 0x88];
|
||||||
|
frame_support::assert_err!(
|
||||||
|
execute(CODE, input.clone(), &mut mock_ext),
|
||||||
|
<Error<Test>>::InputForwarded,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&mock_ext.calls,
|
||||||
|
&[CallEntry {
|
||||||
|
to: ALICE,
|
||||||
|
value: 0x2a,
|
||||||
|
data: input,
|
||||||
|
allows_reentry: false,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-interface")]
|
||||||
|
fn contract_call_clone_input() {
|
||||||
|
const CODE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||||
|
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
(func (export "call")
|
||||||
|
(drop
|
||||||
|
(call $seal_call
|
||||||
|
(i32.const 11) ;; Set FORWARD_INPUT | CLONE_INPUT | ALLOW_REENTRY bits
|
||||||
|
(i32.const 4) ;; Pointer to "callee" address.
|
||||||
|
(i32.const 32) ;; Length of "callee" address.
|
||||||
|
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||||
|
(i32.const 36) ;; Pointer to the buffer with value to transfer
|
||||||
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
|
(i32.const 44) ;; 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; works because the input was cloned
|
||||||
|
(call $seal_input (i32.const 0) (i32.const 44))
|
||||||
|
|
||||||
|
;; return the input to caller for inspection
|
||||||
|
(call $seal_return (i32.const 0) (i32.const 0) (i32.load (i32.const 44)))
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
;; Destination AccountId (ALICE)
|
||||||
|
(data (i32.const 4)
|
||||||
|
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||||
|
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Amount of value to transfer.
|
||||||
|
;; Represented by u64 (8 bytes long) in little endian.
|
||||||
|
(data (i32.const 36) "\2A\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; The input is ignored because we forward our own input
|
||||||
|
(data (i32.const 44) "\01\02\03\04")
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
let mut mock_ext = MockExt::default();
|
||||||
|
let input = vec![0xff, 0x2a, 0x99, 0x88];
|
||||||
|
let result = execute(CODE, input.clone(), &mut mock_ext).unwrap();
|
||||||
|
assert_eq!(result.data.0, input);
|
||||||
|
assert_eq!(
|
||||||
|
&mock_ext.calls,
|
||||||
|
&[CallEntry {
|
||||||
|
to: ALICE,
|
||||||
|
value: 0x2a,
|
||||||
|
data: input,
|
||||||
|
allows_reentry: true,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "unstable-interface")]
|
||||||
|
fn contract_call_tail_call() {
|
||||||
|
const CODE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
(func (export "call")
|
||||||
|
(drop
|
||||||
|
(call $seal_call
|
||||||
|
(i32.const 5) ;; Set FORWARD_INPUT | TAIL_CALL bit
|
||||||
|
(i32.const 4) ;; Pointer to "callee" address.
|
||||||
|
(i32.const 32) ;; Length of "callee" address.
|
||||||
|
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||||
|
(i32.const 36) ;; Pointer to the buffer with value to transfer
|
||||||
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
|
(i32.const 0) ;; Pointer to input data buffer address
|
||||||
|
(i32.const 0) ;; 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; a tail call never returns
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
;; Destination AccountId (ALICE)
|
||||||
|
(data (i32.const 4)
|
||||||
|
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||||
|
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Amount of value to transfer.
|
||||||
|
;; Represented by u64 (8 bytes long) in little endian.
|
||||||
|
(data (i32.const 36) "\2A\00\00\00\00\00\00\00")
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
let mut mock_ext = MockExt::default();
|
||||||
|
let input = vec![0xff, 0x2a, 0x99, 0x88];
|
||||||
|
let result = execute(CODE, input.clone(), &mut mock_ext).unwrap();
|
||||||
|
assert_eq!(result.data, call_return_data());
|
||||||
|
assert_eq!(
|
||||||
|
&mock_ext.calls,
|
||||||
|
&[CallEntry {
|
||||||
|
to: ALICE,
|
||||||
|
value: 0x2a,
|
||||||
|
data: input,
|
||||||
|
allows_reentry: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -772,11 +967,12 @@ mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&mock_ext.transfers,
|
&mock_ext.calls,
|
||||||
&[TransferEntry {
|
&[CallEntry {
|
||||||
to: ALICE,
|
to: ALICE,
|
||||||
value: 6,
|
value: 6,
|
||||||
data: vec![1, 2, 3, 4],
|
data: vec![1, 2, 3, 4],
|
||||||
|
allows_reentry: true,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use crate::{
|
|||||||
wasm::env_def::ConvertibleToWasm,
|
wasm::env_def::ConvertibleToWasm,
|
||||||
schedule::HostFnWeights,
|
schedule::HostFnWeights,
|
||||||
};
|
};
|
||||||
|
use bitflags::bitflags;
|
||||||
use pwasm_utils::parity_wasm::elements::ValueType;
|
use pwasm_utils::parity_wasm::elements::ValueType;
|
||||||
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight};
|
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight};
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
@@ -318,6 +319,47 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Flags used to change the behaviour of `seal_call`.
|
||||||
|
struct CallFlags: u32 {
|
||||||
|
/// Forward the input of current function to the callee.
|
||||||
|
///
|
||||||
|
/// Supplied input pointers are ignored when set.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// A forwarding call will consume the current contracts input. Any attempt to
|
||||||
|
/// access the input after this call returns will lead to [`Error::InputForwarded`].
|
||||||
|
/// It does not matter if this is due to calling `seal_input` or trying another
|
||||||
|
/// forwarding call. Consider using [`Self::CLONE_INPUT`] in order to preserve
|
||||||
|
/// the input.
|
||||||
|
const FORWARD_INPUT = 0b0000_0001;
|
||||||
|
/// Identical to [`Self::FORWARD_INPUT`] but without consuming the input.
|
||||||
|
///
|
||||||
|
/// This adds some additional weight costs to the call.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This implies [`Self::FORWARD_INPUT`] and takes precedence when both are set.
|
||||||
|
const CLONE_INPUT = 0b0000_0010;
|
||||||
|
/// Do not return from the call but rather return the result of the callee to the
|
||||||
|
/// callers caller.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This makes the current contract completely transparent to its caller by replacing
|
||||||
|
/// this contracts potential output by the callee ones. Any code after `seal_call`
|
||||||
|
/// can be safely considered unreachable.
|
||||||
|
const TAIL_CALL = 0b0000_0100;
|
||||||
|
/// Allow the callee to reenter into the current contract.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
const ALLOW_REENTRY = 0b0000_1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This is only appropriate when writing out data of constant size that does not depend on user
|
/// 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
|
/// 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.
|
/// the beginning of the API entry point.
|
||||||
@@ -402,8 +444,7 @@ where
|
|||||||
//
|
//
|
||||||
// Because panics are really undesirable in the runtime code, we treat this as
|
// Because panics are really undesirable in the runtime code, we treat this as
|
||||||
// a trap for now. Eventually, we might want to revisit this.
|
// a trap for now. Eventually, we might want to revisit this.
|
||||||
Err(sp_sandbox::Error::Module) =>
|
Err(sp_sandbox::Error::Module) => Err("validation error")?,
|
||||||
Err("validation error")?,
|
|
||||||
// Any other kind of a trap should result in a failure.
|
// Any other kind of a trap should result in a failure.
|
||||||
Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) =>
|
Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) =>
|
||||||
Err(Error::<E::T>::ContractTrapped)?
|
Err(Error::<E::T>::ContractTrapped)?
|
||||||
@@ -629,6 +670,65 @@ where
|
|||||||
(err, _) => Self::err_into_return_code(err)
|
(err, _) => Self::err_into_return_code(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
flags: CallFlags,
|
||||||
|
callee_ptr: u32,
|
||||||
|
callee_len: u32,
|
||||||
|
gas: u64,
|
||||||
|
value_ptr: u32,
|
||||||
|
value_len: u32,
|
||||||
|
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, callee_len)?;
|
||||||
|
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr, value_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) {
|
||||||
|
self.input_data.take().ok_or_else(|| Error::<E::T>::InputForwarded)?
|
||||||
|
} else {
|
||||||
|
self.read_sandbox_memory(input_data_ptr, input_data_len)?
|
||||||
|
};
|
||||||
|
if value > 0u32.into() {
|
||||||
|
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
||||||
|
}
|
||||||
|
let charged = self.charge_gas(
|
||||||
|
RuntimeCosts::CallSurchargeCodeSize(<E::T as Config>::Schedule::get().limits.code_len)
|
||||||
|
)?;
|
||||||
|
let ext = &mut self.ext;
|
||||||
|
let call_outcome = ext.call(
|
||||||
|
gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY),
|
||||||
|
);
|
||||||
|
let code_len = match &call_outcome {
|
||||||
|
Ok((_, len)) => len,
|
||||||
|
Err((_, len)) => len,
|
||||||
|
};
|
||||||
|
self.adjust_gas(charged, RuntimeCosts::CallSurchargeCodeSize(*code_len));
|
||||||
|
|
||||||
|
// `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to
|
||||||
|
// a halt anyways without anymore code being executed.
|
||||||
|
if flags.contains(CallFlags::TAIL_CALL) {
|
||||||
|
if let Ok((return_value, _)) = call_outcome {
|
||||||
|
return Err(TrapReason::Return(ReturnData {
|
||||||
|
flags: return_value.flags.bits(),
|
||||||
|
data: return_value.data.0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok((output, _)) = &call_outcome {
|
||||||
|
self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
|
||||||
|
Some(RuntimeCosts::CallCopyOut(len))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(Runtime::<E>::exec_into_return_code(call_outcome.map(|r| r.0).map_err(|r| r.0))?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
@@ -758,6 +858,36 @@ define_env!(Env, <E: Ext>,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Make a call to another contract.
|
||||||
|
//
|
||||||
|
// This is equivalent to calling the newer version of this function with
|
||||||
|
// `flags` set to `ALLOW_REENTRY`. See the newer version for documentation.
|
||||||
|
[seal0] seal_call(
|
||||||
|
ctx,
|
||||||
|
callee_ptr: u32,
|
||||||
|
callee_len: u32,
|
||||||
|
gas: u64,
|
||||||
|
value_ptr: u32,
|
||||||
|
value_len: u32,
|
||||||
|
input_data_ptr: u32,
|
||||||
|
input_data_len: u32,
|
||||||
|
output_ptr: u32,
|
||||||
|
output_len_ptr: u32
|
||||||
|
) -> ReturnCode => {
|
||||||
|
ctx.call(
|
||||||
|
CallFlags::ALLOW_REENTRY,
|
||||||
|
callee_ptr,
|
||||||
|
callee_len,
|
||||||
|
gas,
|
||||||
|
value_ptr,
|
||||||
|
value_len,
|
||||||
|
input_data_ptr,
|
||||||
|
input_data_len,
|
||||||
|
output_ptr,
|
||||||
|
output_len_ptr,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
// Make a call to another contract.
|
// Make a call to another contract.
|
||||||
//
|
//
|
||||||
// The callees output buffer is copied to `output_ptr` and its length to `output_len_ptr`.
|
// The callees output buffer is copied to `output_ptr` and its length to `output_len_ptr`.
|
||||||
@@ -766,6 +896,7 @@ define_env!(Env, <E: Ext>,
|
|||||||
//
|
//
|
||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
|
// - flags: See [`CallFlags`] for a documenation of the supported flags.
|
||||||
// - callee_ptr: a pointer to the address of the callee contract.
|
// - callee_ptr: a pointer to the address of the callee contract.
|
||||||
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
||||||
// - callee_len: length of the address buffer.
|
// - callee_len: length of the address buffer.
|
||||||
@@ -789,8 +920,9 @@ define_env!(Env, <E: Ext>,
|
|||||||
// `ReturnCode::BelowSubsistenceThreshold`
|
// `ReturnCode::BelowSubsistenceThreshold`
|
||||||
// `ReturnCode::TransferFailed`
|
// `ReturnCode::TransferFailed`
|
||||||
// `ReturnCode::NotCallable`
|
// `ReturnCode::NotCallable`
|
||||||
[seal0] seal_call(
|
[__unstable__] seal_call(
|
||||||
ctx,
|
ctx,
|
||||||
|
flags: u32,
|
||||||
callee_ptr: u32,
|
callee_ptr: u32,
|
||||||
callee_len: u32,
|
callee_len: u32,
|
||||||
gas: u64,
|
gas: u64,
|
||||||
@@ -801,30 +933,18 @@ define_env!(Env, <E: Ext>,
|
|||||||
output_ptr: u32,
|
output_ptr: u32,
|
||||||
output_len_ptr: u32
|
output_len_ptr: u32
|
||||||
) -> ReturnCode => {
|
) -> ReturnCode => {
|
||||||
ctx.charge_gas(RuntimeCosts::CallBase(input_data_len))?;
|
ctx.call(
|
||||||
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
|
CallFlags::from_bits(flags).ok_or_else(|| "used rerved bit in CallFlags")?,
|
||||||
ctx.read_sandbox_memory_as(callee_ptr, callee_len)?;
|
callee_ptr,
|
||||||
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr, value_len)?;
|
callee_len,
|
||||||
let input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
|
gas,
|
||||||
if value > 0u32.into() {
|
value_ptr,
|
||||||
ctx.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
value_len,
|
||||||
}
|
input_data_ptr,
|
||||||
let charged = ctx.charge_gas(
|
input_data_len,
|
||||||
RuntimeCosts::CallSurchargeCodeSize(<E::T as Config>::Schedule::get().limits.code_len)
|
output_ptr,
|
||||||
)?;
|
output_len_ptr,
|
||||||
let ext = &mut ctx.ext;
|
)
|
||||||
let call_outcome = ext.call(gas, callee, value, input_data);
|
|
||||||
let code_len = match &call_outcome {
|
|
||||||
Ok((_, len)) => len,
|
|
||||||
Err((_, len)) => len,
|
|
||||||
};
|
|
||||||
ctx.adjust_gas(charged, RuntimeCosts::CallSurchargeCodeSize(*code_len));
|
|
||||||
if let Ok((output, _)) = &call_outcome {
|
|
||||||
ctx.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
|
|
||||||
Some(RuntimeCosts::CallCopyOut(len))
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
Ok(Runtime::<E>::exec_into_return_code(call_outcome.map(|r| r.0).map_err(|r| r.0))?)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Instantiate a contract with the specified code hash.
|
// Instantiate a contract with the specified code hash.
|
||||||
@@ -945,7 +1065,6 @@ define_env!(Env, <E: Ext>,
|
|||||||
ctx.charge_gas(RuntimeCosts::Terminate)?;
|
ctx.charge_gas(RuntimeCosts::Terminate)?;
|
||||||
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
|
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
|
||||||
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
|
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
|
||||||
|
|
||||||
let charged = ctx.charge_gas(
|
let charged = ctx.charge_gas(
|
||||||
RuntimeCosts::TerminateSurchargeCodeSize(
|
RuntimeCosts::TerminateSurchargeCodeSize(
|
||||||
<E::T as Config>::Schedule::get().limits.code_len
|
<E::T as Config>::Schedule::get().limits.code_len
|
||||||
@@ -969,16 +1088,17 @@ define_env!(Env, <E: Ext>,
|
|||||||
//
|
//
|
||||||
// # Note
|
// # Note
|
||||||
//
|
//
|
||||||
// This function can only be called once. Calling it multiple times will trigger a trap.
|
// This function traps if the input was previously forwarded by a `seal_call`.
|
||||||
[seal0] seal_input(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
[seal0] seal_input(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||||
ctx.charge_gas(RuntimeCosts::InputBase)?;
|
ctx.charge_gas(RuntimeCosts::InputBase)?;
|
||||||
if let Some(input) = ctx.input_data.take() {
|
if let Some(input) = ctx.input_data.take() {
|
||||||
ctx.write_sandbox_output(out_ptr, out_len_ptr, &input, false, |len| {
|
ctx.write_sandbox_output(out_ptr, out_len_ptr, &input, false, |len| {
|
||||||
Some(RuntimeCosts::InputCopyOut(len))
|
Some(RuntimeCosts::InputCopyOut(len))
|
||||||
})?;
|
})?;
|
||||||
|
ctx.input_data = Some(input);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::<E::T>::InputAlreadyRead.into())
|
Err(Error::<E::T>::InputForwarded.into())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user