mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 07:37:57 +00:00
seal: Rework contracts API (#6573)
* Transition getter functions to not use scratch buffer * Remove scratch buffer from ext_get_storage * Remove scratch buffer from ext_call * Remove scratch buffer from ext_instantiate * Add ext_input and remove scratch buffer * Rework error handling (changes RPC exposed data) * ext_return passes a flags field instead of a return code * Flags is only for seal and not for the caller * flags: u32 replaced status_code: u8 in RPC exposed type * API functions use a unified error type (ReturnCode) * ext_transfer now traps on error to be consistent with call and instantiate * Remove the no longer used `Dispatched` event * Updated inline documentation * Prevent skipping of copying the output for getter API * Return gas_consumed from the RPC contracts call interface * Updated COMPLEXTITY.md * Rename ext_gas_price to ext_weight_to_fee * Align comments with spaces * Removed no longer used `ExecError` * Remove possible panic in `from_typed_value` * Use a struct as associated data for SpecialTrap::Return * Fix nits in COMPLEXITY.md * Renamed SpecialTrap to TrapReason * Fix test * Finish renaming special_trap -> trap_reason * Remove no longer used get_runtime_storage * fixup! Remove no longer used get_runtime_storage * Removed tabs for comment aligment
This commit is contained in:
committed by
GitHub
parent
a4427f3635
commit
25de5b5c78
@@ -19,11 +19,12 @@ use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
|
||||
use crate::gas::{Gas, GasMeter, Token};
|
||||
use crate::rent;
|
||||
use crate::storage;
|
||||
use bitflags::bitflags;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::traits::{Bounded, Zero, Convert};
|
||||
use frame_support::{
|
||||
storage::unhashed, dispatch::DispatchError,
|
||||
dispatch::DispatchError,
|
||||
traits::{ExistenceRequirement, Currency, Time, Randomness},
|
||||
weights::Weight,
|
||||
};
|
||||
@@ -37,58 +38,31 @@ pub type StorageKey = [u8; 32];
|
||||
/// A type that represents a topic of an event. At the moment a hash is used.
|
||||
pub type TopicOf<T> = <T as frame_system::Trait>::Hash;
|
||||
|
||||
/// A status code return to the source of a contract call or instantiation indicating success or
|
||||
/// failure. A code of 0 indicates success and that changes are applied. All other codes indicate
|
||||
/// failure and that changes are reverted. The particular code in the case of failure is opaque and
|
||||
/// may be interpreted by the calling contract.
|
||||
pub type StatusCode = u8;
|
||||
|
||||
/// The status code indicating success.
|
||||
pub const STATUS_SUCCESS: StatusCode = 0;
|
||||
bitflags! {
|
||||
/// Flags used by a contract to customize exit behaviour.
|
||||
pub struct ReturnFlags: u32 {
|
||||
/// If this bit is set all changes made by the contract exection are rolled back.
|
||||
const REVERT = 0x0000_0001;
|
||||
}
|
||||
}
|
||||
|
||||
/// Output of a contract call or instantiation which ran to completion.
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
pub struct ExecReturnValue {
|
||||
pub status: StatusCode,
|
||||
/// Flags passed along by `ext_return`. Empty when `ext_return` was never called.
|
||||
pub flags: ReturnFlags,
|
||||
/// Buffer passed along by `ext_return`. Empty when `ext_return` was never called.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ExecReturnValue {
|
||||
/// Returns whether the call or instantiation exited with a successful status code.
|
||||
/// We understand the absense of a revert flag as success.
|
||||
pub fn is_success(&self) -> bool {
|
||||
self.status == STATUS_SUCCESS
|
||||
!self.flags.contains(ReturnFlags::REVERT)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error indicating some failure to execute a contract call or instantiation. This can include
|
||||
/// VM-specific errors during execution (eg. division by 0, OOB access, failure to satisfy some
|
||||
/// precondition of a system call, etc.) or errors with the orchestration (eg. out-of-gas errors, a
|
||||
/// non-existent destination contract, etc.).
|
||||
#[cfg_attr(test, derive(sp_runtime::RuntimeDebug))]
|
||||
pub struct ExecError {
|
||||
pub reason: DispatchError,
|
||||
/// This is an allocated buffer that may be reused. The buffer must be cleared explicitly
|
||||
/// before reuse.
|
||||
pub buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
pub type ExecResult = Result<ExecReturnValue, ExecError>;
|
||||
|
||||
/// Evaluate an expression of type Result<_, &'static str> and either resolve to the value if Ok or
|
||||
/// wrap the error string into an ExecutionError with the provided buffer and return from the
|
||||
/// enclosing function. This macro is used instead of .map_err(..)? in order to avoid taking
|
||||
/// ownership of buffer unless there is an error.
|
||||
#[macro_export]
|
||||
macro_rules! try_or_exec_error {
|
||||
($e:expr, $buffer:expr) => {
|
||||
match $e {
|
||||
Ok(val) => val,
|
||||
Err(reason) => return Err(
|
||||
$crate::exec::ExecError { reason: reason.into(), buffer: $buffer }
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type ExecResult = Result<ExecReturnValue, DispatchError>;
|
||||
|
||||
/// An interface that provides access to the external environment in which the
|
||||
/// smart-contract is executed.
|
||||
@@ -118,7 +92,7 @@ pub trait Ext {
|
||||
value: BalanceOf<Self::T>,
|
||||
gas_meter: &mut GasMeter<Self::T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
|
||||
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), DispatchError>;
|
||||
|
||||
/// Transfer some amount of funds into the specified account.
|
||||
fn transfer(
|
||||
@@ -208,11 +182,6 @@ pub trait Ext {
|
||||
/// Returns the maximum allowed size of a storage item.
|
||||
fn max_value_size(&self) -> u32;
|
||||
|
||||
/// Returns the value of runtime under the given key.
|
||||
///
|
||||
/// Returns `None` if the value doesn't exist.
|
||||
fn get_runtime_storage(&self, key: &[u8]) -> Option<Vec<u8>>;
|
||||
|
||||
/// Returns the price for the specified amount of weight.
|
||||
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T>;
|
||||
}
|
||||
@@ -331,20 +300,14 @@ where
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
if self.depth == self.config.max_depth as usize {
|
||||
return Err(ExecError {
|
||||
reason: "reached maximum depth, cannot make a call".into(),
|
||||
buffer: input_data,
|
||||
});
|
||||
Err("reached maximum depth, cannot make a call")?
|
||||
}
|
||||
|
||||
if gas_meter
|
||||
.charge(self.config, ExecFeeToken::Call)
|
||||
.is_out_of_gas()
|
||||
{
|
||||
return Err(ExecError {
|
||||
reason: "not enough gas to pay base call fee".into(),
|
||||
buffer: input_data,
|
||||
});
|
||||
Err("not enough gas to pay base call fee")?
|
||||
}
|
||||
|
||||
// Assumption: `collect_rent` doesn't collide with overlay because
|
||||
@@ -354,10 +317,7 @@ where
|
||||
|
||||
// Calls to dead contracts always fail.
|
||||
if let Some(ContractInfo::Tombstone(_)) = contract_info {
|
||||
return Err(ExecError {
|
||||
reason: "contract has been evicted".into(),
|
||||
buffer: input_data,
|
||||
});
|
||||
Err("contract has been evicted")?
|
||||
};
|
||||
|
||||
let caller = self.self_account.clone();
|
||||
@@ -365,27 +325,21 @@ where
|
||||
|
||||
self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
|
||||
if value > BalanceOf::<T>::zero() {
|
||||
try_or_exec_error!(
|
||||
transfer(
|
||||
gas_meter,
|
||||
TransferCause::Call,
|
||||
&caller,
|
||||
&dest,
|
||||
value,
|
||||
nested,
|
||||
),
|
||||
input_data
|
||||
);
|
||||
transfer(
|
||||
gas_meter,
|
||||
TransferCause::Call,
|
||||
&caller,
|
||||
&dest,
|
||||
value,
|
||||
nested,
|
||||
)?
|
||||
}
|
||||
|
||||
// If code_hash is not none, then the destination account is a live contract, otherwise
|
||||
// it is a regular account since tombstone accounts have already been rejected.
|
||||
match storage::code_hash::<T>(&dest) {
|
||||
Ok(dest_code_hash) => {
|
||||
let executable = try_or_exec_error!(
|
||||
nested.loader.load_main(&dest_code_hash),
|
||||
input_data
|
||||
);
|
||||
let executable = nested.loader.load_main(&dest_code_hash)?;
|
||||
let output = nested.vm
|
||||
.execute(
|
||||
&executable,
|
||||
@@ -395,7 +349,7 @@ where
|
||||
)?;
|
||||
Ok(output)
|
||||
}
|
||||
Err(storage::ContractAbsentError) => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }),
|
||||
Err(storage::ContractAbsentError) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -406,22 +360,16 @@ where
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
code_hash: &CodeHash<T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
|
||||
) -> Result<(T::AccountId, ExecReturnValue), DispatchError> {
|
||||
if self.depth == self.config.max_depth as usize {
|
||||
return Err(ExecError {
|
||||
reason: "reached maximum depth, cannot instantiate".into(),
|
||||
buffer: input_data,
|
||||
});
|
||||
Err("reached maximum depth, cannot instantiate")?
|
||||
}
|
||||
|
||||
if gas_meter
|
||||
.charge(self.config, ExecFeeToken::Instantiate)
|
||||
.is_out_of_gas()
|
||||
{
|
||||
return Err(ExecError {
|
||||
reason: "not enough gas to pay base instantiate fee".into(),
|
||||
buffer: input_data,
|
||||
});
|
||||
Err("not enough gas to pay base instantiate fee")?
|
||||
}
|
||||
|
||||
let caller = self.self_account.clone();
|
||||
@@ -437,36 +385,27 @@ where
|
||||
let dest_trie_id = <T as Trait>::TrieIdGenerator::trie_id(&dest);
|
||||
|
||||
let output = self.with_nested_context(dest.clone(), Some(dest_trie_id), |nested| {
|
||||
try_or_exec_error!(
|
||||
storage::place_contract::<T>(
|
||||
&dest,
|
||||
nested
|
||||
.self_trie_id
|
||||
.clone()
|
||||
.expect("the nested context always has to have self_trie_id"),
|
||||
code_hash.clone()
|
||||
),
|
||||
input_data
|
||||
);
|
||||
storage::place_contract::<T>(
|
||||
&dest,
|
||||
nested
|
||||
.self_trie_id
|
||||
.clone()
|
||||
.expect("the nested context always has to have self_trie_id"),
|
||||
code_hash.clone()
|
||||
)?;
|
||||
|
||||
// Send funds unconditionally here. If the `endowment` is below existential_deposit
|
||||
// then error will be returned here.
|
||||
try_or_exec_error!(
|
||||
transfer(
|
||||
gas_meter,
|
||||
TransferCause::Instantiate,
|
||||
&caller,
|
||||
&dest,
|
||||
endowment,
|
||||
nested,
|
||||
),
|
||||
input_data
|
||||
);
|
||||
transfer(
|
||||
gas_meter,
|
||||
TransferCause::Instantiate,
|
||||
&caller,
|
||||
&dest,
|
||||
endowment,
|
||||
nested,
|
||||
)?;
|
||||
|
||||
let executable = try_or_exec_error!(
|
||||
nested.loader.load_init(&code_hash),
|
||||
input_data
|
||||
);
|
||||
let executable = nested.loader.load_init(&code_hash)?;
|
||||
let output = nested.vm
|
||||
.execute(
|
||||
&executable,
|
||||
@@ -477,10 +416,7 @@ where
|
||||
|
||||
// Error out if insufficient remaining balance.
|
||||
if T::Currency::free_balance(&dest) < nested.config.existential_deposit {
|
||||
return Err(ExecError {
|
||||
reason: "insufficient remaining balance".into(),
|
||||
buffer: output.data,
|
||||
});
|
||||
Err("insufficient remaining balance")?
|
||||
}
|
||||
|
||||
// Deposit an instantiation event.
|
||||
@@ -518,7 +454,7 @@ where
|
||||
frame_support::storage::with_transaction(|| {
|
||||
let output = func(&mut nested);
|
||||
match output {
|
||||
Ok(ref rv) if rv.is_success() => Commit(output),
|
||||
Ok(ref rv) if !rv.flags.contains(ReturnFlags::REVERT) => Commit(output),
|
||||
_ => Rollback(output),
|
||||
}
|
||||
})
|
||||
@@ -681,7 +617,7 @@ where
|
||||
endowment: BalanceOf<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
input_data: Vec<u8>,
|
||||
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
|
||||
) -> Result<(AccountIdOf<T>, ExecReturnValue), DispatchError> {
|
||||
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data)
|
||||
}
|
||||
|
||||
@@ -839,10 +775,6 @@ where
|
||||
self.ctx.config.max_value_size
|
||||
}
|
||||
|
||||
fn get_runtime_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
unhashed::get_raw(&key)
|
||||
}
|
||||
|
||||
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
|
||||
T::WeightPrice::convert(weight)
|
||||
}
|
||||
@@ -867,11 +799,11 @@ fn deposit_event<T: Trait>(
|
||||
mod tests {
|
||||
use super::{
|
||||
BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader,
|
||||
RawEvent, TransferFeeKind, TransferFeeToken, Vm,
|
||||
RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags,
|
||||
};
|
||||
use crate::{
|
||||
gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
|
||||
exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config,
|
||||
exec::ExecReturnValue, CodeHash, Config,
|
||||
gas::Gas,
|
||||
storage,
|
||||
};
|
||||
@@ -980,7 +912,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn exec_success() -> ExecResult {
|
||||
Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() })
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1096,7 +1028,7 @@ mod tests {
|
||||
let vm = MockVm::new();
|
||||
let mut loader = MockLoader::empty();
|
||||
let return_ch = loader.insert(
|
||||
|_| Ok(ExecReturnValue { status: 1, data: Vec::new() })
|
||||
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() })
|
||||
);
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
@@ -1228,10 +1160,7 @@ mod tests {
|
||||
|
||||
assert_matches!(
|
||||
result,
|
||||
Err(ExecError {
|
||||
reason: DispatchError::Module { message: Some("InsufficientBalance"), .. },
|
||||
buffer: _,
|
||||
})
|
||||
Err(DispatchError::Module { message: Some("InsufficientBalance"), .. })
|
||||
);
|
||||
assert_eq!(get_balance(&origin), 0);
|
||||
assert_eq!(get_balance(&dest), 0);
|
||||
@@ -1248,7 +1177,7 @@ mod tests {
|
||||
let vm = MockVm::new();
|
||||
let mut loader = MockLoader::empty();
|
||||
let return_ch = loader.insert(
|
||||
|_| Ok(ExecReturnValue { status: STATUS_SUCCESS, data: vec![1, 2, 3, 4] })
|
||||
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] })
|
||||
);
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
@@ -1279,7 +1208,7 @@ mod tests {
|
||||
let vm = MockVm::new();
|
||||
let mut loader = MockLoader::empty();
|
||||
let return_ch = loader.insert(
|
||||
|_| Ok(ExecReturnValue { status: 1, data: vec![1, 2, 3, 4] })
|
||||
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] })
|
||||
);
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
@@ -1370,10 +1299,7 @@ mod tests {
|
||||
// Verify that we've got proper error and set `reached_bottom`.
|
||||
assert_matches!(
|
||||
r,
|
||||
Err(ExecError {
|
||||
reason: DispatchError::Other("reached maximum depth, cannot make a call"),
|
||||
buffer: _,
|
||||
})
|
||||
Err(DispatchError::Other("reached maximum depth, cannot make a call"))
|
||||
);
|
||||
*reached_bottom = true;
|
||||
} else {
|
||||
@@ -1517,7 +1443,7 @@ mod tests {
|
||||
|
||||
let mut loader = MockLoader::empty();
|
||||
let dummy_ch = loader.insert(
|
||||
|_| Ok(ExecReturnValue { status: STATUS_SUCCESS, data: vec![80, 65, 83, 83] })
|
||||
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
|
||||
);
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
@@ -1550,7 +1476,7 @@ mod tests {
|
||||
|
||||
let mut loader = MockLoader::empty();
|
||||
let dummy_ch = loader.insert(
|
||||
|_| Ok(ExecReturnValue { status: 1, data: vec![70, 65, 73, 76] })
|
||||
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
|
||||
);
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
@@ -1627,7 +1553,7 @@ mod tests {
|
||||
|
||||
let mut loader = MockLoader::empty();
|
||||
let dummy_ch = loader.insert(
|
||||
|_| Err(ExecError { reason: "It's a trap!".into(), buffer: Vec::new() })
|
||||
|_| Err("It's a trap!".into())
|
||||
);
|
||||
let instantiator_ch = loader.insert({
|
||||
let dummy_ch = dummy_ch.clone();
|
||||
@@ -1640,7 +1566,7 @@ mod tests {
|
||||
ctx.gas_meter,
|
||||
vec![]
|
||||
),
|
||||
Err(ExecError { reason: DispatchError::Other("It's a trap!"), buffer: _ })
|
||||
Err(DispatchError::Other("It's a trap!"))
|
||||
);
|
||||
|
||||
exec_success()
|
||||
@@ -1691,10 +1617,7 @@ mod tests {
|
||||
&terminate_ch,
|
||||
vec![],
|
||||
),
|
||||
Err(ExecError {
|
||||
reason: DispatchError::Other("insufficient remaining balance"),
|
||||
buffer
|
||||
}) if buffer == Vec::<u8>::new()
|
||||
Err(DispatchError::Other("insufficient remaining balance"))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user