mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 00:01:03 +00:00
seal: Fix and improve error reporting (#6773)
* seal: Rework ext_transfer, ext_instantiate, ext_call error handling * Deny calling plain accounts (must use transfer now) * Return proper module error rather than ad-hoc strings * Return the correct error codes from call,instantiate (documentation was wrong) * Make ext_transfer fallible again to make it consistent with ext_call * seal: Improve error messages on memory access failures * seal: Convert contract trapped to module error * seal: Add additional tests for transfer, call, instantiate These tests verify that those functions return the error types which are declared in its docs. * Make it more pronounced that to_execution_result handles trap_reason * Improve ReturnCode docs * Fix whitespace issues in wat files * Improve ReturnCode doc * Improve ErrorOrigin doc and variant naming * Improve docs on ExecResult and ExecError * Encode u32 sentinel value as hex * with_nested_context no longer accepts an Option for trie * Fix successful typo * Rename InvalidContractCalled to NotCallable
This commit is contained in:
committed by
GitHub
parent
0553dabe32
commit
6671d017d6
@@ -0,0 +1,44 @@
|
|||||||
|
;; This calls Django (4) and transfers 100 balance during this call and copies the return code
|
||||||
|
;; of this call to the output buffer.
|
||||||
|
;; It also forwards its input to the callee.
|
||||||
|
(module
|
||||||
|
(import "env" "ext_input" (func $ext_input (param i32 i32)))
|
||||||
|
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "ext_return" (func $ext_return (param i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
;; [0, 8) address of django
|
||||||
|
(data (i32.const 0) "\04\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; [8, 16) 100 balance
|
||||||
|
(data (i32.const 8) "\64\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; [16, 20) here we store the return code of the transfer
|
||||||
|
|
||||||
|
;; [20, 24) here we store the input data
|
||||||
|
|
||||||
|
;; [24, 28) size of the input data
|
||||||
|
(data (i32.const 24) "\04")
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
(call $ext_input (i32.const 20) (i32.const 24))
|
||||||
|
(i32.store
|
||||||
|
(i32.const 16)
|
||||||
|
(call $ext_call
|
||||||
|
(i32.const 0) ;; Pointer to "callee" address.
|
||||||
|
(i32.const 8) ;; Length of "callee" address.
|
||||||
|
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||||
|
(i32.const 8) ;; Pointer to the buffer with value to transfer
|
||||||
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
|
(i32.const 20) ;; Pointer to input data buffer address
|
||||||
|
(i32.load (i32.const 24)) ;; Length of input data buffer
|
||||||
|
(i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output
|
||||||
|
(i32.const 0) ;; Ptr to output buffer len
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;; exit with success and take transfer return code to the output buffer
|
||||||
|
(call $ext_return (i32.const 0) (i32.const 16) (i32.const 4))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
(call $ext_instantiate
|
(call $ext_instantiate
|
||||||
(i32.const 24) ;; Pointer to the code hash.
|
(i32.const 24) ;; Pointer to the code hash.
|
||||||
(i32.const 32) ;; Length of the code hash.
|
(i32.const 32) ;; Length of the code hash.
|
||||||
(i64.const 200) ;; How much gas to devote for the execution.
|
(i64.const 187500000) ;; Just enough to pay for the instantiate
|
||||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
(i32.const 8) ;; Pointer to input data buffer address
|
(i32.const 8) ;; Pointer to input data buffer address
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
(call $ext_call
|
(call $ext_call
|
||||||
(i32.const 16) ;; Pointer to "callee" address.
|
(i32.const 16) ;; Pointer to "callee" address.
|
||||||
(i32.const 8) ;; Length of "callee" address.
|
(i32.const 8) ;; Length of "callee" address.
|
||||||
(i64.const 100) ;; How much gas to devote for the execution.
|
(i64.const 117500000) ;; Just enough to make the call
|
||||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
(i32.const 8) ;; Pointer to input data buffer address
|
(i32.const 8) ;; Pointer to input data buffer address
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
(import "env" "ext_get_storage" (func $ext_get_storage (param i32 i32 i32) (result i32)))
|
(import "env" "ext_get_storage" (func $ext_get_storage (param i32 i32 i32) (result i32)))
|
||||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
||||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32)))
|
||||||
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
|
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
@@ -139,16 +140,11 @@
|
|||||||
;; does not keep the contract alive.
|
;; does not keep the contract alive.
|
||||||
(call $assert
|
(call $assert
|
||||||
(i32.eq
|
(i32.eq
|
||||||
(call $ext_call
|
(call $ext_transfer
|
||||||
(i32.const 80) ;; Pointer to destination address
|
(i32.const 80) ;; Pointer to destination address
|
||||||
(i32.const 8) ;; Length of destination address
|
(i32.const 8) ;; Length of destination address
|
||||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
|
||||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||||
(i32.const 8) ;; Length of 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 1) ;; Length of input data buffer
|
|
||||||
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
|
|
||||||
(i32.const 0) ;; Length is ignored in this case
|
|
||||||
)
|
)
|
||||||
(i32.const 0)
|
(i32.const 0)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
(module
|
(module
|
||||||
(import "env" "ext_balance" (func $ext_balance (param i32 i32)))
|
(import "env" "ext_balance" (func $ext_balance (param i32 i32)))
|
||||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32)))
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
;; [0, 8) reserved for $ext_balance output
|
;; [0, 8) reserved for $ext_balance output
|
||||||
@@ -36,18 +36,13 @@
|
|||||||
;; Self-destruct by sending full balance to the 0 address.
|
;; Self-destruct by sending full balance to the 0 address.
|
||||||
(call $assert
|
(call $assert
|
||||||
(i32.eq
|
(i32.eq
|
||||||
(call $ext_call
|
(call $ext_transfer
|
||||||
(i32.const 16) ;; Pointer to destination address
|
(i32.const 16) ;; Pointer to destination address
|
||||||
(i32.const 8) ;; Length of destination address
|
(i32.const 8) ;; Length of destination address
|
||||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
|
||||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||||
(i32.const 8) ;; Length of 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 sentinel value: do not copy output
|
|
||||||
(i32.const 0) ;; Length is ignored in this case
|
|
||||||
)
|
)
|
||||||
(i32.const 0)
|
(i32.const 4) ;; ReturnCode::BelowSubsistenceThreshold
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
;; This instantiats Charlie (3) and transfers 100 balance during this call and copies the return code
|
||||||
|
;; of this call to the output buffer.
|
||||||
|
;; The first 32 byte of input is the code hash to instantiate
|
||||||
|
;; The rest of the input is forwarded to the constructor of the callee
|
||||||
|
(module
|
||||||
|
(import "env" "ext_input" (func $ext_input (param i32 i32)))
|
||||||
|
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "ext_return" (func $ext_return (param i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
;; [0, 8) address of django
|
||||||
|
(data (i32.const 0) "\04\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; [8, 16) 100 balance
|
||||||
|
(data (i32.const 8) "\64\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; [16, 20) here we store the return code of the transfer
|
||||||
|
|
||||||
|
;; [20, 24) size of the input buffer
|
||||||
|
(data (i32.const 20) "\FF")
|
||||||
|
|
||||||
|
;; [24, inf) input buffer
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
(call $ext_input (i32.const 24) (i32.const 20))
|
||||||
|
(i32.store
|
||||||
|
(i32.const 16)
|
||||||
|
(call $ext_instantiate
|
||||||
|
(i32.const 24) ;; Pointer to the code hash.
|
||||||
|
(i32.const 32) ;; Length of the code hash.
|
||||||
|
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||||
|
(i32.const 8) ;; Pointer to the buffer with value to transfer
|
||||||
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
|
(i32.const 56) ;; Pointer to input data buffer address
|
||||||
|
(i32.sub (i32.load (i32.const 20)) (i32.const 32)) ;; Length of input data buffer
|
||||||
|
(i32.const 0xffffffff) ;; u32 max sentinel value: do not copy address
|
||||||
|
(i32.const 0) ;; Length is ignored in this case
|
||||||
|
(i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output
|
||||||
|
(i32.const 0) ;; Length is ignored in this case
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;; exit with success and take transfer return code to the output buffer
|
||||||
|
(call $ext_return (i32.const 0) (i32.const 16) (i32.const 4))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
(module
|
||||||
|
(import "env" "ext_input" (func $ext_input (param i32 i32)))
|
||||||
|
(import "env" "ext_return" (func $ext_return (param i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
(func (export "deploy")
|
||||||
|
(call $ok_trap_revert)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
(call $ok_trap_revert)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func $ok_trap_revert
|
||||||
|
(i32.store (i32.const 4) (i32.const 4))
|
||||||
|
(call $ext_input (i32.const 0) (i32.const 4))
|
||||||
|
(block $IF_2
|
||||||
|
(block $IF_1
|
||||||
|
(block $IF_0
|
||||||
|
(br_table $IF_0 $IF_1 $IF_2
|
||||||
|
(i32.load8_u (i32.const 0))
|
||||||
|
)
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
;; 0 = return with success
|
||||||
|
return
|
||||||
|
)
|
||||||
|
;; 1 = revert
|
||||||
|
(call $ext_return (i32.const 1) (i32.const 0) (i32.const 0))
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
;; 2 = trap
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
(module
|
(module
|
||||||
(import "env" "ext_balance" (func $ext_balance (param i32 i32)))
|
(import "env" "ext_terminate" (func $ext_terminate (param i32 i32)))
|
||||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
|
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
;; [0, 8) reserved for $ext_balance output
|
|
||||||
|
|
||||||
;; [8, 16) length of the buffer
|
|
||||||
(data (i32.const 8) "\08")
|
|
||||||
|
|
||||||
;; [16, inf) zero initialized
|
|
||||||
|
|
||||||
(func $assert (param i32)
|
(func $assert (param i32)
|
||||||
(block $ok
|
(block $ok
|
||||||
(br_if $ok
|
(br_if $ok
|
||||||
@@ -20,33 +12,10 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
(func (export "deploy")
|
(func (export "deploy")
|
||||||
;; Send entire remaining balance to the 0 address.
|
|
||||||
(call $ext_balance (i32.const 0) (i32.const 8))
|
|
||||||
|
|
||||||
;; Balance should be encoded as a u64.
|
|
||||||
(call $assert
|
|
||||||
(i32.eq
|
|
||||||
(i32.load (i32.const 8))
|
|
||||||
(i32.const 8)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Self-destruct by sending full balance to the 0 address.
|
;; Self-destruct by sending full balance to the 0 address.
|
||||||
(call $assert
|
(call $ext_terminate
|
||||||
(i32.eq
|
(i32.const 0) ;; Pointer to destination address
|
||||||
(call $ext_call
|
(i32.const 8) ;; Length of destination address
|
||||||
(i32.const 16) ;; Pointer to destination address
|
|
||||||
(i32.const 8) ;; Length of destination address
|
|
||||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
|
||||||
(i32.const 0) ;; 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 sentinel value: do not copy output
|
|
||||||
(i32.const 0) ;; Length is ignored in this case
|
|
||||||
)
|
|
||||||
(i32.const 0)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
(module
|
(module
|
||||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32)))
|
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32)))
|
||||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
||||||
(import "env" "ext_clear_storage" (func $ext_clear_storage (param i32)))
|
(import "env" "ext_clear_storage" (func $ext_clear_storage (param i32)))
|
||||||
(import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
|
(import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
|
||||||
@@ -24,7 +24,12 @@
|
|||||||
|
|
||||||
;; transfer 50 to CHARLIE
|
;; transfer 50 to CHARLIE
|
||||||
(func $call_2
|
(func $call_2
|
||||||
(call $ext_transfer (i32.const 68) (i32.const 8) (i32.const 76) (i32.const 8))
|
(call $assert
|
||||||
|
(i32.eq
|
||||||
|
(call $ext_transfer (i32.const 68) (i32.const 8) (i32.const 76) (i32.const 8))
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; do nothing
|
;; do nothing
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
;; This transfers 100 balance to the zero account and copies the return code
|
||||||
|
;; of this transfer to the output buffer.
|
||||||
|
(module
|
||||||
|
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32)))
|
||||||
|
(import "env" "ext_return" (func $ext_return (param i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
;; [0, 8) zero-adress
|
||||||
|
(data (i32.const 0) "\00\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; [8, 16) 100 balance
|
||||||
|
(data (i32.const 8) "\64\00\00\00\00\00\00\00")
|
||||||
|
|
||||||
|
;; [16, 20) here we store the return code of the transfer
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
(i32.store
|
||||||
|
(i32.const 16)
|
||||||
|
(call $ext_transfer
|
||||||
|
(i32.const 0) ;; ptr to destination address
|
||||||
|
(i32.const 8) ;; length of destination address
|
||||||
|
(i32.const 8) ;; ptr to value to transfer
|
||||||
|
(i32.const 8) ;; length of value to transfer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;; exit with success and take transfer return code to the output buffer
|
||||||
|
(call $ext_return (i32.const 0) (i32.const 16) (i32.const 4))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -14,9 +14,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
|
use crate::{
|
||||||
TrieId, BalanceOf, ContractInfo, TrieIdGenerator};
|
CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
|
||||||
use crate::{gas::{Gas, GasMeter, Token}, rent, storage, Error, ContractInfoOf};
|
TrieId, BalanceOf, ContractInfo, TrieIdGenerator,
|
||||||
|
gas::{Gas, GasMeter, Token}, rent, storage, Error, ContractInfoOf
|
||||||
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
|
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
|
||||||
@@ -69,7 +71,39 @@ impl ExecReturnValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ExecResult = Result<ExecReturnValue, DispatchError>;
|
/// Call or instantiate both call into other contracts and pass through errors happening
|
||||||
|
/// in those to the caller. This enum is for the caller to distinguish whether the error
|
||||||
|
/// happened during the execution of the callee or in the current execution context.
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||||
|
pub enum ErrorOrigin {
|
||||||
|
/// The error happened in the current exeuction context rather than in the one
|
||||||
|
/// of the contract that is called into.
|
||||||
|
Caller,
|
||||||
|
/// The error happened during execution of the called contract.
|
||||||
|
Callee,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error returned by contract exection.
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||||
|
pub struct ExecError {
|
||||||
|
/// The reason why the execution failed.
|
||||||
|
pub error: DispatchError,
|
||||||
|
/// Origin of the error.
|
||||||
|
pub origin: ErrorOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<DispatchError>> From<T> for ExecError {
|
||||||
|
fn from(error: T) -> Self {
|
||||||
|
Self {
|
||||||
|
error: error.into(),
|
||||||
|
origin: ErrorOrigin::Caller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result that is returned from contract execution. It either contains the output
|
||||||
|
/// buffer or an error describing the reason for failure.
|
||||||
|
pub type ExecResult = Result<ExecReturnValue, ExecError>;
|
||||||
|
|
||||||
/// An interface that provides access to the external environment in which the
|
/// An interface that provides access to the external environment in which the
|
||||||
/// smart-contract is executed.
|
/// smart-contract is executed.
|
||||||
@@ -99,7 +133,7 @@ pub trait Ext {
|
|||||||
value: BalanceOf<Self::T>,
|
value: BalanceOf<Self::T>,
|
||||||
gas_meter: &mut GasMeter<Self::T>,
|
gas_meter: &mut GasMeter<Self::T>,
|
||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), DispatchError>;
|
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
|
||||||
|
|
||||||
/// Transfer some amount of funds into the specified account.
|
/// Transfer some amount of funds into the specified account.
|
||||||
fn transfer(
|
fn transfer(
|
||||||
@@ -282,12 +316,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: Option<TrieId>)
|
fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: TrieId)
|
||||||
-> ExecutionContext<'b, T, V, L>
|
-> ExecutionContext<'b, T, V, L>
|
||||||
{
|
{
|
||||||
ExecutionContext {
|
ExecutionContext {
|
||||||
caller: Some(self),
|
caller: Some(self),
|
||||||
self_trie_id: trie_id,
|
self_trie_id: Some(trie_id),
|
||||||
self_account: dest,
|
self_account: dest,
|
||||||
depth: self.depth + 1,
|
depth: self.depth + 1,
|
||||||
config: self.config,
|
config: self.config,
|
||||||
@@ -307,31 +341,31 @@ where
|
|||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
) -> ExecResult {
|
) -> ExecResult {
|
||||||
if self.depth == self.config.max_depth as usize {
|
if self.depth == self.config.max_depth as usize {
|
||||||
Err("reached maximum depth, cannot make a call")?
|
Err(Error::<T>::MaxCallDepthReached)?
|
||||||
}
|
}
|
||||||
|
|
||||||
if gas_meter
|
if gas_meter
|
||||||
.charge(self.config, ExecFeeToken::Call)
|
.charge(self.config, ExecFeeToken::Call)
|
||||||
.is_out_of_gas()
|
.is_out_of_gas()
|
||||||
{
|
{
|
||||||
Err("not enough gas to pay base call fee")?
|
Err(Error::<T>::OutOfGas)?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumption: `collect_rent` doesn't collide with overlay because
|
// Assumption: `collect_rent` doesn't collide with overlay because
|
||||||
// `collect_rent` will be done on first call and destination contract and balance
|
// `collect_rent` will be done on first call and destination contract and balance
|
||||||
// cannot be changed before the first call
|
// cannot be changed before the first call
|
||||||
let contract_info = rent::collect_rent::<T>(&dest);
|
// We do not allow 'calling' plain accounts. For transfering value
|
||||||
|
// `ext_transfer` must be used.
|
||||||
// Calls to dead contracts always fail.
|
let contract = if let Some(ContractInfo::Alive(info)) = rent::collect_rent::<T>(&dest) {
|
||||||
if let Some(ContractInfo::Tombstone(_)) = contract_info {
|
info
|
||||||
Err("contract has been evicted")?
|
} else {
|
||||||
|
Err(Error::<T>::NotCallable)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let transactor_kind = self.transactor_kind();
|
let transactor_kind = self.transactor_kind();
|
||||||
let caller = self.self_account.clone();
|
let caller = self.self_account.clone();
|
||||||
let dest_trie_id = contract_info.and_then(|i| i.as_alive().map(|i| i.trie_id.clone()));
|
|
||||||
|
|
||||||
self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
|
self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| {
|
||||||
if value > BalanceOf::<T>::zero() {
|
if value > BalanceOf::<T>::zero() {
|
||||||
transfer(
|
transfer(
|
||||||
gas_meter,
|
gas_meter,
|
||||||
@@ -344,22 +378,15 @@ where
|
|||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
// If code_hash is not none, then the destination account is a live contract, otherwise
|
let executable = nested.loader.load_main(&contract.code_hash)
|
||||||
// it is a regular account since tombstone accounts have already been rejected.
|
.map_err(|_| Error::<T>::CodeNotFound)?;
|
||||||
match storage::code_hash::<T>(&dest) {
|
let output = nested.vm.execute(
|
||||||
Ok(dest_code_hash) => {
|
&executable,
|
||||||
let executable = nested.loader.load_main(&dest_code_hash)?;
|
nested.new_call_context(caller, value),
|
||||||
let output = nested.vm
|
input_data,
|
||||||
.execute(
|
gas_meter,
|
||||||
&executable,
|
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
|
||||||
nested.new_call_context(caller, value),
|
Ok(output)
|
||||||
input_data,
|
|
||||||
gas_meter,
|
|
||||||
)?;
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
Err(storage::ContractAbsentError) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,16 +396,16 @@ where
|
|||||||
gas_meter: &mut GasMeter<T>,
|
gas_meter: &mut GasMeter<T>,
|
||||||
code_hash: &CodeHash<T>,
|
code_hash: &CodeHash<T>,
|
||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
) -> Result<(T::AccountId, ExecReturnValue), DispatchError> {
|
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
|
||||||
if self.depth == self.config.max_depth as usize {
|
if self.depth == self.config.max_depth as usize {
|
||||||
Err("reached maximum depth, cannot instantiate")?
|
Err(Error::<T>::MaxCallDepthReached)?
|
||||||
}
|
}
|
||||||
|
|
||||||
if gas_meter
|
if gas_meter
|
||||||
.charge(self.config, ExecFeeToken::Instantiate)
|
.charge(self.config, ExecFeeToken::Instantiate)
|
||||||
.is_out_of_gas()
|
.is_out_of_gas()
|
||||||
{
|
{
|
||||||
Err("not enough gas to pay base instantiate fee")?
|
Err(Error::<T>::OutOfGas)?
|
||||||
}
|
}
|
||||||
|
|
||||||
let transactor_kind = self.transactor_kind();
|
let transactor_kind = self.transactor_kind();
|
||||||
@@ -394,7 +421,7 @@ where
|
|||||||
// Generate it now.
|
// Generate it now.
|
||||||
let dest_trie_id = <T as Trait>::TrieIdGenerator::trie_id(&dest);
|
let dest_trie_id = <T as Trait>::TrieIdGenerator::trie_id(&dest);
|
||||||
|
|
||||||
let output = self.with_nested_context(dest.clone(), Some(dest_trie_id), |nested| {
|
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
|
||||||
storage::place_contract::<T>(
|
storage::place_contract::<T>(
|
||||||
&dest,
|
&dest,
|
||||||
nested
|
nested
|
||||||
@@ -416,21 +443,21 @@ where
|
|||||||
nested,
|
nested,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let executable = nested.loader.load_init(&code_hash)?;
|
let executable = nested.loader.load_init(&code_hash)
|
||||||
|
.map_err(|_| Error::<T>::CodeNotFound)?;
|
||||||
let output = nested.vm
|
let output = nested.vm
|
||||||
.execute(
|
.execute(
|
||||||
&executable,
|
&executable,
|
||||||
nested.new_call_context(caller.clone(), endowment),
|
nested.new_call_context(caller.clone(), endowment),
|
||||||
input_data,
|
input_data,
|
||||||
gas_meter,
|
gas_meter,
|
||||||
)?;
|
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
|
||||||
|
|
||||||
// Error out if insufficient remaining balance.
|
|
||||||
// We need each contract that exists to be above the subsistence threshold
|
// We need each contract that exists to be above the subsistence threshold
|
||||||
// in order to keep up the guarantuee that we always leave a tombstone behind
|
// in order to keep up the guarantuee that we always leave a tombstone behind
|
||||||
// with the exception of a contract that called `ext_terminate`.
|
// with the exception of a contract that called `ext_terminate`.
|
||||||
if T::Currency::free_balance(&dest) < nested.config.subsistence_threshold() {
|
if T::Currency::total_balance(&dest) < nested.config.subsistence_threshold() {
|
||||||
Err("insufficient remaining balance")?
|
Err(Error::<T>::NewContractNotFunded)?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deposit an instantiation event.
|
// Deposit an instantiation event.
|
||||||
@@ -459,7 +486,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the given closure within a nested execution context.
|
/// Execute the given closure within a nested execution context.
|
||||||
fn with_nested_context<F>(&mut self, dest: T::AccountId, trie_id: Option<TrieId>, func: F)
|
fn with_nested_context<F>(&mut self, dest: T::AccountId, trie_id: TrieId, func: F)
|
||||||
-> ExecResult
|
-> ExecResult
|
||||||
where F: FnOnce(&mut ExecutionContext<T, V, L>) -> ExecResult
|
where F: FnOnce(&mut ExecutionContext<T, V, L>) -> ExecResult
|
||||||
{
|
{
|
||||||
@@ -569,7 +596,7 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if gas_meter.charge(ctx.config, token).is_out_of_gas() {
|
if gas_meter.charge(ctx.config, token).is_out_of_gas() {
|
||||||
Err("not enough gas to pay transfer fee")?
|
Err(Error::<T>::OutOfGas)?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only ext_terminate is allowed to bring the sender below the subsistence
|
// Only ext_terminate is allowed to bring the sender below the subsistence
|
||||||
@@ -580,13 +607,15 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
|
|||||||
ensure!(
|
ensure!(
|
||||||
T::Currency::total_balance(transactor).saturating_sub(value) >=
|
T::Currency::total_balance(transactor).saturating_sub(value) >=
|
||||||
ctx.config.subsistence_threshold(),
|
ctx.config.subsistence_threshold(),
|
||||||
Error::<T>::InsufficientBalance,
|
Error::<T>::BelowSubsistenceThreshold,
|
||||||
);
|
);
|
||||||
ExistenceRequirement::KeepAlive
|
ExistenceRequirement::KeepAlive
|
||||||
},
|
},
|
||||||
(_, PlainAccount) => ExistenceRequirement::KeepAlive,
|
(_, PlainAccount) => ExistenceRequirement::KeepAlive,
|
||||||
};
|
};
|
||||||
T::Currency::transfer(transactor, dest, value, existence_requirement)?;
|
|
||||||
|
T::Currency::transfer(transactor, dest, value, existence_requirement)
|
||||||
|
.map_err(|_| Error::<T>::TransferFailed)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -653,7 +682,7 @@ where
|
|||||||
endowment: BalanceOf<T>,
|
endowment: BalanceOf<T>,
|
||||||
gas_meter: &mut GasMeter<T>,
|
gas_meter: &mut GasMeter<T>,
|
||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
) -> Result<(AccountIdOf<T>, ExecReturnValue), DispatchError> {
|
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
|
||||||
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data)
|
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,13 +866,13 @@ fn deposit_event<T: Trait>(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader,
|
BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader,
|
||||||
RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags,
|
RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags, ExecError, ErrorOrigin
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
|
gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
|
||||||
exec::ExecReturnValue, CodeHash, Config,
|
exec::ExecReturnValue, CodeHash, Config,
|
||||||
gas::Gas,
|
gas::Gas,
|
||||||
storage,
|
storage, Error
|
||||||
};
|
};
|
||||||
use crate::tests::test_utils::{place_contract, set_balance, get_balance};
|
use crate::tests::test_utils::{place_contract, set_balance, get_balance};
|
||||||
use sp_runtime::DispatchError;
|
use sp_runtime::DispatchError;
|
||||||
@@ -999,11 +1028,19 @@ mod tests {
|
|||||||
|
|
||||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
|
|
||||||
let result = ctx.call(dest, 0, &mut gas_meter, vec![]);
|
let result = super::transfer(
|
||||||
|
&mut gas_meter,
|
||||||
|
super::TransferCause::Call,
|
||||||
|
super::TransactorKind::PlainAccount,
|
||||||
|
&origin,
|
||||||
|
&dest,
|
||||||
|
0,
|
||||||
|
&mut ctx,
|
||||||
|
);
|
||||||
assert_matches!(result, Ok(_));
|
assert_matches!(result, Ok(_));
|
||||||
|
|
||||||
let mut toks = gas_meter.tokens().iter();
|
let mut toks = gas_meter.tokens().iter();
|
||||||
match_tokens!(toks, ExecFeeToken::Call,);
|
match_tokens!(toks, TransferFeeToken { kind: TransferFeeKind::Transfer },);
|
||||||
});
|
});
|
||||||
|
|
||||||
// This test verifies that base fee for instantiation is taken.
|
// This test verifies that base fee for instantiation is taken.
|
||||||
@@ -1043,14 +1080,18 @@ mod tests {
|
|||||||
set_balance(&origin, 100);
|
set_balance(&origin, 100);
|
||||||
set_balance(&dest, 0);
|
set_balance(&dest, 0);
|
||||||
|
|
||||||
let output = ctx.call(
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
dest,
|
|
||||||
|
super::transfer(
|
||||||
|
&mut gas_meter,
|
||||||
|
super::TransferCause::Call,
|
||||||
|
super::TransactorKind::PlainAccount,
|
||||||
|
&origin,
|
||||||
|
&dest,
|
||||||
55,
|
55,
|
||||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
&mut ctx,
|
||||||
vec![],
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert!(output.is_success());
|
|
||||||
assert_eq!(get_balance(&origin), 45);
|
assert_eq!(get_balance(&origin), 45);
|
||||||
assert_eq!(get_balance(&dest), 55);
|
assert_eq!(get_balance(&dest), 55);
|
||||||
});
|
});
|
||||||
@@ -1107,13 +1148,20 @@ mod tests {
|
|||||||
|
|
||||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
|
|
||||||
let result = ctx.call(dest, 50, &mut gas_meter, vec![]);
|
let result = super::transfer(
|
||||||
|
&mut gas_meter,
|
||||||
|
super::TransferCause::Call,
|
||||||
|
super::TransactorKind::PlainAccount,
|
||||||
|
&origin,
|
||||||
|
&dest,
|
||||||
|
50,
|
||||||
|
&mut ctx,
|
||||||
|
);
|
||||||
assert_matches!(result, Ok(_));
|
assert_matches!(result, Ok(_));
|
||||||
|
|
||||||
let mut toks = gas_meter.tokens().iter();
|
let mut toks = gas_meter.tokens().iter();
|
||||||
match_tokens!(
|
match_tokens!(
|
||||||
toks,
|
toks,
|
||||||
ExecFeeToken::Call,
|
|
||||||
TransferFeeToken {
|
TransferFeeToken {
|
||||||
kind: TransferFeeKind::Transfer,
|
kind: TransferFeeKind::Transfer,
|
||||||
},
|
},
|
||||||
@@ -1132,13 +1180,20 @@ mod tests {
|
|||||||
|
|
||||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
|
|
||||||
let result = ctx.call(dest, 50, &mut gas_meter, vec![]);
|
let result = super::transfer(
|
||||||
|
&mut gas_meter,
|
||||||
|
super::TransferCause::Call,
|
||||||
|
super::TransactorKind::PlainAccount,
|
||||||
|
&origin,
|
||||||
|
&dest,
|
||||||
|
50,
|
||||||
|
&mut ctx,
|
||||||
|
);
|
||||||
assert_matches!(result, Ok(_));
|
assert_matches!(result, Ok(_));
|
||||||
|
|
||||||
let mut toks = gas_meter.tokens().iter();
|
let mut toks = gas_meter.tokens().iter();
|
||||||
match_tokens!(
|
match_tokens!(
|
||||||
toks,
|
toks,
|
||||||
ExecFeeToken::Call,
|
|
||||||
TransferFeeToken {
|
TransferFeeToken {
|
||||||
kind: TransferFeeKind::Transfer,
|
kind: TransferFeeKind::Transfer,
|
||||||
},
|
},
|
||||||
@@ -1189,16 +1244,19 @@ mod tests {
|
|||||||
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
||||||
set_balance(&origin, 0);
|
set_balance(&origin, 0);
|
||||||
|
|
||||||
let result = ctx.call(
|
let result = super::transfer(
|
||||||
dest,
|
|
||||||
100,
|
|
||||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||||
vec![],
|
super::TransferCause::Call,
|
||||||
|
super::TransactorKind::PlainAccount,
|
||||||
|
&origin,
|
||||||
|
&dest,
|
||||||
|
100,
|
||||||
|
&mut ctx,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Err(DispatchError::Module { message: Some("InsufficientBalance"), .. })
|
Err(Error::<Test>::TransferFailed.into())
|
||||||
);
|
);
|
||||||
assert_eq!(get_balance(&origin), 0);
|
assert_eq!(get_balance(&origin), 0);
|
||||||
assert_eq!(get_balance(&dest), 0);
|
assert_eq!(get_balance(&dest), 0);
|
||||||
@@ -1335,9 +1393,9 @@ mod tests {
|
|||||||
if !*reached_bottom {
|
if !*reached_bottom {
|
||||||
// We are first time here, it means we just reached bottom.
|
// We are first time here, it means we just reached bottom.
|
||||||
// Verify that we've got proper error and set `reached_bottom`.
|
// Verify that we've got proper error and set `reached_bottom`.
|
||||||
assert_matches!(
|
assert_eq!(
|
||||||
r,
|
r,
|
||||||
Err(DispatchError::Other("reached maximum depth, cannot make a call"))
|
Err(Error::<Test>::MaxCallDepthReached.into())
|
||||||
);
|
);
|
||||||
*reached_bottom = true;
|
*reached_bottom = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -1604,7 +1662,10 @@ mod tests {
|
|||||||
ctx.gas_meter,
|
ctx.gas_meter,
|
||||||
vec![]
|
vec![]
|
||||||
),
|
),
|
||||||
Err(DispatchError::Other("It's a trap!"))
|
Err(ExecError {
|
||||||
|
error: DispatchError::Other("It's a trap!"),
|
||||||
|
origin: ErrorOrigin::Callee,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
exec_success()
|
exec_success()
|
||||||
@@ -1648,14 +1709,14 @@ mod tests {
|
|||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
set_balance(&ALICE, 1000);
|
set_balance(&ALICE, 1000);
|
||||||
|
|
||||||
assert_matches!(
|
assert_eq!(
|
||||||
ctx.instantiate(
|
ctx.instantiate(
|
||||||
100,
|
100,
|
||||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||||
&terminate_ch,
|
&terminate_ch,
|
||||||
vec![],
|
vec![],
|
||||||
),
|
),
|
||||||
Err(DispatchError::Other("insufficient remaining balance"))
|
Err(Error::<Test>::NewContractNotFunded.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -14,11 +14,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::Trait;
|
use crate::{Trait, exec::ExecError};
|
||||||
use sp_std::marker::PhantomData;
|
use sp_std::marker::PhantomData;
|
||||||
use sp_runtime::traits::Zero;
|
use sp_runtime::traits::Zero;
|
||||||
use frame_support::dispatch::{
|
use frame_support::dispatch::{
|
||||||
DispatchError, DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo,
|
DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -189,8 +189,9 @@ impl<T: Trait> GasMeter<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Turn this GasMeter into a DispatchResult that contains the actually used gas.
|
/// Turn this GasMeter into a DispatchResult that contains the actually used gas.
|
||||||
pub fn into_dispatch_result<R, E>(self, result: Result<R, E>) -> DispatchResultWithPostInfo where
|
pub fn into_dispatch_result<R, E>(self, result: Result<R, E>) -> DispatchResultWithPostInfo
|
||||||
E: Into<DispatchError>,
|
where
|
||||||
|
E: Into<ExecError>,
|
||||||
{
|
{
|
||||||
let post_info = PostDispatchInfo {
|
let post_info = PostDispatchInfo {
|
||||||
actual_weight: Some(self.gas_spent()),
|
actual_weight: Some(self.gas_spent()),
|
||||||
@@ -199,7 +200,7 @@ impl<T: Trait> GasMeter<T> {
|
|||||||
|
|
||||||
result
|
result
|
||||||
.map(|_| post_info)
|
.map(|_| post_info)
|
||||||
.map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into() })
|
.map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ use crate::wasm::{WasmLoader, WasmVm};
|
|||||||
|
|
||||||
pub use crate::gas::{Gas, GasMeter};
|
pub use crate::gas::{Gas, GasMeter};
|
||||||
pub use crate::exec::{ExecResult, ExecReturnValue};
|
pub use crate::exec::{ExecResult, ExecReturnValue};
|
||||||
|
pub use crate::wasm::ReturnCode as RuntimeReturnCode;
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
@@ -420,9 +421,30 @@ decl_error! {
|
|||||||
/// the subsistence threshold. No transfer is allowed to do this in order to allow
|
/// the subsistence threshold. No transfer is allowed to do this in order to allow
|
||||||
/// for a tombstone to be created. Use `ext_terminate` to remove a contract without
|
/// for a tombstone to be created. Use `ext_terminate` to remove a contract without
|
||||||
/// leaving a tombstone behind.
|
/// leaving a tombstone behind.
|
||||||
InsufficientBalance,
|
BelowSubsistenceThreshold,
|
||||||
|
/// The newly created contract is below the subsistence threshold after executing
|
||||||
|
/// its contructor. No contracts are allowed to exist below that threshold.
|
||||||
|
NewContractNotFunded,
|
||||||
|
/// Performing the requested transfer failed for a reason originating in the
|
||||||
|
/// chosen currency implementation of the runtime. Most probably the balance is
|
||||||
|
/// too low or locks are placed on it.
|
||||||
|
TransferFailed,
|
||||||
|
/// Performing a call was denied because the calling depth reached the limit
|
||||||
|
/// of what is specified in the schedule.
|
||||||
|
MaxCallDepthReached,
|
||||||
|
/// The contract that was called is either no contract at all (a plain account)
|
||||||
|
/// or is a tombstone.
|
||||||
|
NotCallable,
|
||||||
/// The code supplied to `put_code` exceeds the limit specified in the current schedule.
|
/// The code supplied to `put_code` exceeds the limit specified in the current schedule.
|
||||||
CodeTooLarge,
|
CodeTooLarge,
|
||||||
|
/// No code could be found at the supplied code hash.
|
||||||
|
CodeNotFound,
|
||||||
|
/// A buffer outside of sandbox memory was passed to a contract API function.
|
||||||
|
OutOfBounds,
|
||||||
|
/// Input passed to a contract API function failed to decode as expected type.
|
||||||
|
DecodingFailed,
|
||||||
|
/// Contract trapped during execution.
|
||||||
|
ContractTrapped,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ pub fn set_rent_allowance<T: Trait>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the code hash of the contract specified by `account` ID.
|
/// Returns the code hash of the contract specified by `account` ID.
|
||||||
|
#[cfg(test)]
|
||||||
pub fn code_hash<T: Trait>(account: &AccountIdOf<T>) -> Result<CodeHash<T>, ContractAbsentError> {
|
pub fn code_hash<T: Trait>(account: &AccountIdOf<T>) -> Result<CodeHash<T>, ContractAbsentError> {
|
||||||
<ContractInfoOf<T>>::get(account)
|
<ContractInfoOf<T>>::get(account)
|
||||||
.and_then(|i| i.as_alive().map(|i| i.code_hash))
|
.and_then(|i| i.as_alive().map(|i| i.code_hash))
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
BalanceOf, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, Module,
|
BalanceOf, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, Module,
|
||||||
RawAliveContractInfo, RawEvent, Trait, TrieId, Schedule, TrieIdGenerator, gas::Gas,
|
RawAliveContractInfo, RawEvent, Trait, TrieId, Schedule, TrieIdGenerator, gas::Gas,
|
||||||
Error,
|
Error, Config, RuntimeReturnCode,
|
||||||
};
|
};
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use hex_literal::*;
|
use hex_literal::*;
|
||||||
@@ -30,8 +30,9 @@ use sp_runtime::{
|
|||||||
use frame_support::{
|
use frame_support::{
|
||||||
assert_ok, assert_err_ignore_postinfo, impl_outer_dispatch, impl_outer_event,
|
assert_ok, assert_err_ignore_postinfo, impl_outer_dispatch, impl_outer_event,
|
||||||
impl_outer_origin, parameter_types, StorageMap, StorageValue,
|
impl_outer_origin, parameter_types, StorageMap, StorageValue,
|
||||||
traits::{Currency, Get},
|
traits::{Currency, Get, ReservableCurrency},
|
||||||
weights::{Weight, PostDispatchInfo},
|
weights::{Weight, PostDispatchInfo},
|
||||||
|
dispatch::DispatchErrorWithPostInfo,
|
||||||
};
|
};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use frame_system::{self as system, EventRecord, Phase};
|
use frame_system::{self as system, EventRecord, Phase};
|
||||||
@@ -63,6 +64,7 @@ impl_outer_dispatch! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use super::{Test, Balances};
|
use super::{Test, Balances};
|
||||||
use crate::{ContractInfoOf, TrieIdGenerator, CodeHash};
|
use crate::{ContractInfoOf, TrieIdGenerator, CodeHash};
|
||||||
@@ -89,6 +91,12 @@ pub mod test_utils {
|
|||||||
pub fn get_balance(who: &u64) -> u64 {
|
pub fn get_balance(who: &u64) -> u64 {
|
||||||
Balances::free_balance(who)
|
Balances::free_balance(who)
|
||||||
}
|
}
|
||||||
|
macro_rules! assert_return_code {
|
||||||
|
( $x:expr , $y:expr $(,)? ) => {{
|
||||||
|
use sp_std::convert::TryInto;
|
||||||
|
assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32);
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
@@ -279,19 +287,23 @@ where
|
|||||||
Ok((wasm_binary, code_hash))
|
Ok((wasm_binary, code_hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform a simple transfer to a non-existent account.
|
// Perform a call to a plain account.
|
||||||
|
// The actual transfer fails because we can only call contracts.
|
||||||
// Then we check that only the base costs are returned as actual costs.
|
// Then we check that only the base costs are returned as actual costs.
|
||||||
#[test]
|
#[test]
|
||||||
fn returns_base_call_cost() {
|
fn calling_plain_account_fails() {
|
||||||
ExtBuilder::default().build().execute_with(|| {
|
ExtBuilder::default().build().execute_with(|| {
|
||||||
let _ = Balances::deposit_creating(&ALICE, 100_000_000);
|
let _ = Balances::deposit_creating(&ALICE, 100_000_000);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()),
|
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()),
|
||||||
Ok(
|
Err(
|
||||||
PostDispatchInfo {
|
DispatchErrorWithPostInfo {
|
||||||
actual_weight: Some(67500000),
|
error: Error::<Test>::NotCallable.into(),
|
||||||
pays_fee: Default::default(),
|
post_info: PostDispatchInfo {
|
||||||
|
actual_weight: Some(67500000),
|
||||||
|
pays_fee: Default::default(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -987,7 +999,7 @@ fn call_removed_contract() {
|
|||||||
// Calling contract should remove contract and fail.
|
// Calling contract should remove contract and fail.
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, call::null()),
|
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, call::null()),
|
||||||
"contract has been evicted"
|
Error::<Test>::NotCallable
|
||||||
);
|
);
|
||||||
// Calling a contract that is about to evict shall emit an event.
|
// Calling a contract that is about to evict shall emit an event.
|
||||||
assert_eq!(System::events(), vec![
|
assert_eq!(System::events(), vec![
|
||||||
@@ -1001,7 +1013,7 @@ fn call_removed_contract() {
|
|||||||
// Subsequent contract calls should also fail.
|
// Subsequent contract calls should also fail.
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, call::null()),
|
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, call::null()),
|
||||||
"contract has been evicted"
|
Error::<Test>::NotCallable
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1128,7 +1140,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
|
|||||||
// we expect that it will get removed leaving tombstone.
|
// we expect that it will get removed leaving tombstone.
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, call::null()),
|
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, call::null()),
|
||||||
"contract has been evicted"
|
Error::<Test>::NotCallable
|
||||||
);
|
);
|
||||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||||
assert_eq!(System::events(), vec![
|
assert_eq!(System::events(), vec![
|
||||||
@@ -1181,7 +1193,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
|
|||||||
|
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
perform_the_restoration(),
|
perform_the_restoration(),
|
||||||
"contract trapped during execution"
|
Error::<Test>::ContractTrapped,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||||
@@ -1309,7 +1321,7 @@ fn storage_max_value_limit() {
|
|||||||
GAS_LIMIT,
|
GAS_LIMIT,
|
||||||
Encode::encode(&(self::MaxValueSize::get() + 1)),
|
Encode::encode(&(self::MaxValueSize::get() + 1)),
|
||||||
),
|
),
|
||||||
"contract trapped during execution"
|
Error::<Test>::ContractTrapped,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1373,17 +1385,16 @@ fn cannot_self_destruct_through_draning() {
|
|||||||
Some(ContractInfo::Alive(_))
|
Some(ContractInfo::Alive(_))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Call BOB with no input data, forcing it to run until out-of-balance
|
// Call BOB which makes it send all funds to the zero address
|
||||||
// and eventually trapping because below existential deposit.
|
// The contract code asserts that the correct error value is returned.
|
||||||
assert_err_ignore_postinfo!(
|
assert_ok!(
|
||||||
Contracts::call(
|
Contracts::call(
|
||||||
Origin::signed(ALICE),
|
Origin::signed(ALICE),
|
||||||
BOB,
|
BOB,
|
||||||
0,
|
0,
|
||||||
GAS_LIMIT,
|
GAS_LIMIT,
|
||||||
vec![],
|
vec![],
|
||||||
),
|
)
|
||||||
"contract trapped during execution"
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1423,7 +1434,7 @@ fn cannot_self_destruct_while_live() {
|
|||||||
GAS_LIMIT,
|
GAS_LIMIT,
|
||||||
vec![0],
|
vec![0],
|
||||||
),
|
),
|
||||||
"contract trapped during execution"
|
Error::<Test>::ContractTrapped,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that BOB is still alive.
|
// Check that BOB is still alive.
|
||||||
@@ -1535,8 +1546,7 @@ fn cannot_self_destruct_in_constructor() {
|
|||||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
|
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
|
||||||
|
|
||||||
// Fail to instantiate the BOB because the call that is issued in the deploy
|
// Fail to instantiate the BOB because the contructor calls ext_terminate.
|
||||||
// function exhausts all balances which puts it below the existential deposit.
|
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
Contracts::instantiate(
|
Contracts::instantiate(
|
||||||
Origin::signed(ALICE),
|
Origin::signed(ALICE),
|
||||||
@@ -1545,7 +1555,7 @@ fn cannot_self_destruct_in_constructor() {
|
|||||||
code_hash.into(),
|
code_hash.into(),
|
||||||
vec![],
|
vec![],
|
||||||
),
|
),
|
||||||
"contract trapped during execution"
|
Error::<Test>::NewContractNotFunded,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1603,3 +1613,216 @@ fn crypto_hashes() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transfer_return_code() {
|
||||||
|
let (wasm, code_hash) = compile_module::<Test>("transfer_return_code").unwrap();
|
||||||
|
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
||||||
|
let subsistence = Config::<Test>::subsistence_threshold_uncached();
|
||||||
|
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
|
||||||
|
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
|
||||||
|
|
||||||
|
assert_ok!(
|
||||||
|
Contracts::instantiate(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
subsistence,
|
||||||
|
GAS_LIMIT,
|
||||||
|
code_hash.into(),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contract has only the minimal balance so any transfer will return BelowSubsistence.
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
|
||||||
|
|
||||||
|
// Contract has enough total balance in order to not go below the subsistence
|
||||||
|
// threshold when transfering 100 balance but this balance is reserved so
|
||||||
|
// the transfer still fails but with another return code.
|
||||||
|
Balances::make_free_balance_be(&BOB, subsistence + 100);
|
||||||
|
Balances::reserve(&BOB, subsistence + 100).unwrap();
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_return_code() {
|
||||||
|
let (caller_code, caller_hash) = compile_module::<Test>("call_return_code").unwrap();
|
||||||
|
let (callee_code, callee_hash) = compile_module::<Test>("ok_trap_revert").unwrap();
|
||||||
|
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
||||||
|
let subsistence = Config::<Test>::subsistence_threshold_uncached();
|
||||||
|
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
|
||||||
|
let _ = Balances::deposit_creating(&CHARLIE, 10 * subsistence);
|
||||||
|
assert_ok!(Contracts::put_code(Origin::signed(ALICE), caller_code));
|
||||||
|
assert_ok!(Contracts::put_code(Origin::signed(ALICE), callee_code));
|
||||||
|
|
||||||
|
assert_ok!(
|
||||||
|
Contracts::instantiate(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
subsistence,
|
||||||
|
GAS_LIMIT,
|
||||||
|
caller_hash.into(),
|
||||||
|
vec![0],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contract calls into Django which is no valid contract
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![0],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::NotCallable);
|
||||||
|
|
||||||
|
assert_ok!(
|
||||||
|
Contracts::instantiate(
|
||||||
|
Origin::signed(CHARLIE),
|
||||||
|
subsistence,
|
||||||
|
GAS_LIMIT,
|
||||||
|
callee_hash.into(),
|
||||||
|
vec![0],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contract has only the minimal balance so any transfer will return BelowSubsistence.
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![0],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
|
||||||
|
|
||||||
|
// Contract has enough total balance in order to not go below the subsistence
|
||||||
|
// threshold when transfering 100 balance but this balance is reserved so
|
||||||
|
// the transfer still fails but with another return code.
|
||||||
|
Balances::make_free_balance_be(&BOB, subsistence + 100);
|
||||||
|
Balances::reserve(&BOB, subsistence + 100).unwrap();
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![0],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||||
|
|
||||||
|
// Contract has enough balance but callee reverts because "1" is passed.
|
||||||
|
Balances::make_free_balance_be(&BOB, subsistence + 1000);
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![1],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
|
||||||
|
|
||||||
|
// Contract has enough balance but callee traps because "2" is passed.
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![2],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instantiate_return_code() {
|
||||||
|
let (caller_code, caller_hash) = compile_module::<Test>("instantiate_return_code").unwrap();
|
||||||
|
let (callee_code, callee_hash) = compile_module::<Test>("ok_trap_revert").unwrap();
|
||||||
|
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
||||||
|
let subsistence = Config::<Test>::subsistence_threshold_uncached();
|
||||||
|
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
|
||||||
|
let _ = Balances::deposit_creating(&CHARLIE, 10 * subsistence);
|
||||||
|
assert_ok!(Contracts::put_code(Origin::signed(ALICE), caller_code));
|
||||||
|
assert_ok!(Contracts::put_code(Origin::signed(ALICE), callee_code));
|
||||||
|
let callee_hash = callee_hash.as_ref().to_vec();
|
||||||
|
|
||||||
|
assert_ok!(
|
||||||
|
Contracts::instantiate(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
subsistence,
|
||||||
|
GAS_LIMIT,
|
||||||
|
caller_hash.into(),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contract has only the minimal balance so any transfer will return BelowSubsistence.
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![0; 33],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
|
||||||
|
|
||||||
|
// Contract has enough total balance in order to not go below the subsistence
|
||||||
|
// threshold when transfering 100 balance but this balance is reserved so
|
||||||
|
// the transfer still fails but with another return code.
|
||||||
|
Balances::make_free_balance_be(&BOB, subsistence + 100);
|
||||||
|
Balances::reserve(&BOB, subsistence + 100).unwrap();
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![0; 33],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
|
||||||
|
|
||||||
|
// Contract has enough balance but the passed code hash is invalid
|
||||||
|
Balances::make_free_balance_be(&BOB, subsistence + 1000);
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
vec![0; 33],
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::CodeNotFound);
|
||||||
|
|
||||||
|
// Contract has enough balance but callee reverts because "1" is passed.
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
callee_hash.iter().cloned().chain(sp_std::iter::once(1)).collect(),
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
|
||||||
|
|
||||||
|
// Contract has enough balance but callee traps because "2" is passed.
|
||||||
|
let result = Contracts::bare_call(
|
||||||
|
ALICE,
|
||||||
|
BOB,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
callee_hash.iter().cloned().chain(sp_std::iter::once(2)).collect(),
|
||||||
|
).0.unwrap();
|
||||||
|
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ use self::runtime::{to_execution_result, Runtime};
|
|||||||
use self::code_cache::load as load_code;
|
use self::code_cache::load as load_code;
|
||||||
|
|
||||||
pub use self::code_cache::save as save_code;
|
pub use self::code_cache::save as save_code;
|
||||||
|
pub use self::runtime::ReturnCode;
|
||||||
|
|
||||||
/// A prepared wasm module ready for execution.
|
/// A prepared wasm module ready for execution.
|
||||||
#[derive(Clone, Encode, Decode)]
|
#[derive(Clone, Encode, Decode)]
|
||||||
@@ -152,13 +153,12 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use sp_core::H256;
|
use sp_core::H256;
|
||||||
use crate::exec::{Ext, StorageKey, ExecReturnValue, ReturnFlags};
|
use crate::exec::{Ext, StorageKey, ExecReturnValue, ReturnFlags, ExecError, ErrorOrigin};
|
||||||
use crate::gas::{Gas, GasMeter};
|
use crate::gas::{Gas, GasMeter};
|
||||||
use crate::tests::{Test, Call};
|
use crate::tests::{Test, Call};
|
||||||
use crate::wasm::prepare::prepare_contract;
|
use crate::wasm::prepare::prepare_contract;
|
||||||
use crate::{CodeHash, BalanceOf};
|
use crate::{CodeHash, BalanceOf, Error};
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
use assert_matches::assert_matches;
|
|
||||||
use sp_runtime::DispatchError;
|
use sp_runtime::DispatchError;
|
||||||
use frame_support::weights::Weight;
|
use frame_support::weights::Weight;
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ mod tests {
|
|||||||
endowment: u64,
|
endowment: u64,
|
||||||
gas_meter: &mut GasMeter<Test>,
|
gas_meter: &mut GasMeter<Test>,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
) -> Result<(u64, ExecReturnValue), DispatchError> {
|
) -> Result<(u64, ExecReturnValue), ExecError> {
|
||||||
self.instantiates.push(InstantiateEntry {
|
self.instantiates.push(InstantiateEntry {
|
||||||
code_hash: code_hash.clone(),
|
code_hash: code_hash.clone(),
|
||||||
endowment,
|
endowment,
|
||||||
@@ -365,7 +365,7 @@ mod tests {
|
|||||||
value: u64,
|
value: u64,
|
||||||
gas_meter: &mut GasMeter<Test>,
|
gas_meter: &mut GasMeter<Test>,
|
||||||
input_data: Vec<u8>,
|
input_data: Vec<u8>,
|
||||||
) -> Result<(u64, ExecReturnValue), DispatchError> {
|
) -> Result<(u64, ExecReturnValue), ExecError> {
|
||||||
(**self).instantiate(code, value, gas_meter, input_data)
|
(**self).instantiate(code, value, gas_meter, input_data)
|
||||||
}
|
}
|
||||||
fn transfer(
|
fn transfer(
|
||||||
@@ -483,14 +483,16 @@ mod tests {
|
|||||||
;; value_ptr: u32,
|
;; value_ptr: u32,
|
||||||
;; value_len: u32,
|
;; value_len: u32,
|
||||||
;;) -> u32
|
;;) -> u32
|
||||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32)))
|
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32)))
|
||||||
(import "env" "memory" (memory 1 1))
|
(import "env" "memory" (memory 1 1))
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
(call $ext_transfer
|
(drop
|
||||||
(i32.const 4) ;; Pointer to "account" address.
|
(call $ext_transfer
|
||||||
(i32.const 8) ;; Length of "account" address.
|
(i32.const 4) ;; Pointer to "account" address.
|
||||||
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
(i32.const 8) ;; Length of "account" address.
|
||||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
(i32.const 12) ;; Pointer to the buffer with value to transfer
|
||||||
|
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
@@ -521,7 +523,7 @@ mod tests {
|
|||||||
to: 7,
|
to: 7,
|
||||||
value: 153,
|
value: 153,
|
||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
gas_left: 9989500000,
|
gas_left: 9989000000,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1503,14 +1505,17 @@ mod tests {
|
|||||||
// Checks that the runtime traps if there are more than `max_topic_events` topics.
|
// Checks that the runtime traps if there are more than `max_topic_events` topics.
|
||||||
let mut gas_meter = GasMeter::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::new(GAS_LIMIT);
|
||||||
|
|
||||||
assert_matches!(
|
assert_eq!(
|
||||||
execute(
|
execute(
|
||||||
CODE_DEPOSIT_EVENT_MAX_TOPICS,
|
CODE_DEPOSIT_EVENT_MAX_TOPICS,
|
||||||
vec![],
|
vec![],
|
||||||
MockExt::default(),
|
MockExt::default(),
|
||||||
&mut gas_meter
|
&mut gas_meter
|
||||||
),
|
),
|
||||||
Err(DispatchError::Other("contract trapped during execution"))
|
Err(ExecError {
|
||||||
|
error: Error::<Test>::ContractTrapped.into(),
|
||||||
|
origin: ErrorOrigin::Caller,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1545,14 +1550,17 @@ mod tests {
|
|||||||
// Checks that the runtime traps if there are duplicates.
|
// Checks that the runtime traps if there are duplicates.
|
||||||
let mut gas_meter = GasMeter::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::new(GAS_LIMIT);
|
||||||
|
|
||||||
assert_matches!(
|
assert_eq!(
|
||||||
execute(
|
execute(
|
||||||
CODE_DEPOSIT_EVENT_DUPLICATES,
|
CODE_DEPOSIT_EVENT_DUPLICATES,
|
||||||
vec![],
|
vec![],
|
||||||
MockExt::default(),
|
MockExt::default(),
|
||||||
&mut gas_meter
|
&mut gas_meter
|
||||||
),
|
),
|
||||||
Err(DispatchError::Other("contract trapped during execution"))
|
Err(ExecError {
|
||||||
|
error: Error::<Test>::ContractTrapped.into(),
|
||||||
|
origin: ErrorOrigin::Caller,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1666,4 +1674,75 @@ mod tests {
|
|||||||
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::REVERT, data: hex!("5566778899").to_vec() });
|
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::REVERT, data: hex!("5566778899").to_vec() });
|
||||||
assert!(!output.is_success());
|
assert!(!output.is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CODE_OUT_OF_BOUNDS_ACCESS: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "env" "ext_terminate" (func $ext_terminate (param i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
(call $ext_terminate
|
||||||
|
(i32.const 65536) ;; Pointer to "account" address (out of bound).
|
||||||
|
(i32.const 8) ;; Length of "account" address.
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn contract_out_of_bounds_access() {
|
||||||
|
let mut mock_ext = MockExt::default();
|
||||||
|
let result = execute(
|
||||||
|
CODE_OUT_OF_BOUNDS_ACCESS,
|
||||||
|
vec![],
|
||||||
|
&mut mock_ext,
|
||||||
|
&mut GasMeter::new(GAS_LIMIT),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Err(ExecError {
|
||||||
|
error: Error::<Test>::OutOfBounds.into(),
|
||||||
|
origin: ErrorOrigin::Caller,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CODE_DECODE_FAILURE: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "env" "ext_terminate" (func $ext_terminate (param i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
(func (export "deploy"))
|
||||||
|
|
||||||
|
(func (export "call")
|
||||||
|
(call $ext_terminate
|
||||||
|
(i32.const 0) ;; Pointer to "account" address.
|
||||||
|
(i32.const 4) ;; Length of "account" address (too small -> decode fail).
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn contract_decode_failure() {
|
||||||
|
let mut mock_ext = MockExt::default();
|
||||||
|
let result = execute(
|
||||||
|
CODE_DECODE_FAILURE,
|
||||||
|
vec![],
|
||||||
|
&mut mock_ext,
|
||||||
|
&mut GasMeter::new(GAS_LIMIT),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Err(ExecError {
|
||||||
|
error: Error::<Test>::DecodingFailed.into(),
|
||||||
|
origin: ErrorOrigin::Caller,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
use crate::{Schedule, Trait, CodeHash, BalanceOf, Error};
|
use crate::{Schedule, Trait, CodeHash, BalanceOf, Error};
|
||||||
use crate::exec::{
|
use crate::exec::{
|
||||||
Ext, ExecResult, ExecReturnValue, StorageKey, TopicOf, ReturnFlags,
|
Ext, ExecResult, ExecReturnValue, StorageKey, TopicOf, ReturnFlags, ExecError
|
||||||
};
|
};
|
||||||
use crate::gas::{Gas, GasMeter, Token, GasMeterResult};
|
use crate::gas::{Gas, GasMeter, Token, GasMeterResult};
|
||||||
use crate::wasm::env_def::ConvertibleToWasm;
|
use crate::wasm::env_def::ConvertibleToWasm;
|
||||||
@@ -36,21 +36,33 @@ use sp_io::hashing::{
|
|||||||
sha2_256,
|
sha2_256,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Every error that can be returned from a runtime API call.
|
/// Every error that can be returned to a contract when it calls any of the host functions.
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum ReturnCode {
|
pub enum ReturnCode {
|
||||||
/// API call successful.
|
/// API call successful.
|
||||||
Success = 0,
|
Success = 0,
|
||||||
/// The called function trapped and has its state changes reverted.
|
/// The called function trapped and has its state changes reverted.
|
||||||
/// In this case no output buffer is returned.
|
/// In this case no output buffer is returned.
|
||||||
/// Can only be returned from `ext_call` and `ext_instantiate`.
|
|
||||||
CalleeTrapped = 1,
|
CalleeTrapped = 1,
|
||||||
/// The called function ran to completion but decided to revert its state.
|
/// The called function ran to completion but decided to revert its state.
|
||||||
/// An output buffer is returned when one was supplied.
|
/// An output buffer is returned when one was supplied.
|
||||||
/// Can only be returned from `ext_call` and `ext_instantiate`.
|
|
||||||
CalleeReverted = 2,
|
CalleeReverted = 2,
|
||||||
/// The passed key does not exist in storage.
|
/// The passed key does not exist in storage.
|
||||||
KeyNotFound = 3,
|
KeyNotFound = 3,
|
||||||
|
/// Transfer failed because it would have brought the sender's total balance below the
|
||||||
|
/// subsistence threshold.
|
||||||
|
BelowSubsistenceThreshold = 4,
|
||||||
|
/// Transfer failed for other reasons. Most probably reserved or locked balance of the
|
||||||
|
/// sender prevents the transfer.
|
||||||
|
TransferFailed = 5,
|
||||||
|
/// The newly created contract is below the subsistence threshold after executing
|
||||||
|
/// its constructor.
|
||||||
|
NewContractNotFunded = 6,
|
||||||
|
/// No code could be found at the supplied code hash.
|
||||||
|
CodeNotFound = 7,
|
||||||
|
/// The contract that was called is either no contract at all (a plain account)
|
||||||
|
/// or is a tombstone.
|
||||||
|
NotCallable = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConvertibleToWasm for ReturnCode {
|
impl ConvertibleToWasm for ReturnCode {
|
||||||
@@ -66,7 +78,7 @@ impl ConvertibleToWasm for ReturnCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<ExecReturnValue> for ReturnCode {
|
impl From<ExecReturnValue> for ReturnCode {
|
||||||
fn from(from: ExecReturnValue) -> ReturnCode {
|
fn from(from: ExecReturnValue) -> Self {
|
||||||
if from.flags.contains(ReturnFlags::REVERT) {
|
if from.flags.contains(ReturnFlags::REVERT) {
|
||||||
Self::CalleeReverted
|
Self::CalleeReverted
|
||||||
} else {
|
} else {
|
||||||
@@ -96,7 +108,7 @@ enum TrapReason {
|
|||||||
SupervisorError(DispatchError),
|
SupervisorError(DispatchError),
|
||||||
/// Signals that trap was generated in response to call `ext_return` host function.
|
/// Signals that trap was generated in response to call `ext_return` host function.
|
||||||
Return(ReturnData),
|
Return(ReturnData),
|
||||||
/// Signals that a trap was generated in response to a succesful call to the
|
/// Signals that a trap was generated in response to a successful call to the
|
||||||
/// `ext_terminate` host function.
|
/// `ext_terminate` host function.
|
||||||
Termination,
|
Termination,
|
||||||
/// Signals that a trap was generated because of a successful restoration.
|
/// Signals that a trap was generated because of a successful restoration.
|
||||||
@@ -131,35 +143,42 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the sandbox result and the runtime state into the execution outcome.
|
||||||
|
///
|
||||||
|
/// It evaluates information stored in the `trap_reason` variable of the runtime and
|
||||||
|
/// bases the outcome on the value if this variable. Only if `trap_reason` is `None`
|
||||||
|
/// the result of the sandbox is evaluated.
|
||||||
pub(crate) fn to_execution_result<E: Ext>(
|
pub(crate) fn to_execution_result<E: Ext>(
|
||||||
runtime: Runtime<E>,
|
runtime: Runtime<E>,
|
||||||
sandbox_result: Result<sp_sandbox::ReturnValue, sp_sandbox::Error>,
|
sandbox_result: Result<sp_sandbox::ReturnValue, sp_sandbox::Error>,
|
||||||
) -> ExecResult {
|
) -> ExecResult {
|
||||||
match runtime.trap_reason {
|
// If a trap reason is set we base our decision solely on that.
|
||||||
// The trap was the result of the execution `return` host function.
|
if let Some(trap_reason) = runtime.trap_reason {
|
||||||
Some(TrapReason::Return(ReturnData{ flags, data })) => {
|
return match trap_reason {
|
||||||
let flags = ReturnFlags::from_bits(flags).ok_or_else(||
|
// The trap was the result of the execution `return` host function.
|
||||||
"used reserved bit in return flags"
|
TrapReason::Return(ReturnData{ flags, data }) => {
|
||||||
)?;
|
let flags = ReturnFlags::from_bits(flags).ok_or_else(||
|
||||||
return Ok(ExecReturnValue {
|
"used reserved bit in return flags"
|
||||||
flags,
|
)?;
|
||||||
data,
|
Ok(ExecReturnValue {
|
||||||
})
|
flags,
|
||||||
},
|
data,
|
||||||
Some(TrapReason::Termination) => {
|
})
|
||||||
return Ok(ExecReturnValue {
|
},
|
||||||
flags: ReturnFlags::empty(),
|
TrapReason::Termination => {
|
||||||
data: Vec::new(),
|
Ok(ExecReturnValue {
|
||||||
})
|
flags: ReturnFlags::empty(),
|
||||||
},
|
data: Vec::new(),
|
||||||
Some(TrapReason::Restoration) => {
|
})
|
||||||
return Ok(ExecReturnValue {
|
},
|
||||||
flags: ReturnFlags::empty(),
|
TrapReason::Restoration => {
|
||||||
data: Vec::new(),
|
Ok(ExecReturnValue {
|
||||||
})
|
flags: ReturnFlags::empty(),
|
||||||
|
data: Vec::new(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
TrapReason::SupervisorError(error) => Err(error)?,
|
||||||
}
|
}
|
||||||
Some(TrapReason::SupervisorError(error)) => Err(error)?,
|
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the exact type of the error.
|
// Check the exact type of the error.
|
||||||
@@ -178,7 +197,7 @@ pub(crate) fn to_execution_result<E: Ext>(
|
|||||||
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("contract trapped during execution")?,
|
Err(Error::<E::T>::ContractTrapped)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +299,8 @@ fn read_sandbox_memory<E: Ext>(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut buf = vec![0u8; len as usize];
|
let mut buf = vec![0u8; len as usize];
|
||||||
ctx.memory.get(ptr, buf.as_mut_slice()).map_err(|_| sp_sandbox::HostError)?;
|
ctx.memory.get(ptr, buf.as_mut_slice())
|
||||||
|
.map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +324,7 @@ fn read_sandbox_memory_into_buf<E: Ext>(
|
|||||||
RuntimeToken::ReadMemory(buf.len() as u32),
|
RuntimeToken::ReadMemory(buf.len() as u32),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
ctx.memory.get(ptr, buf).map_err(Into::into)
|
ctx.memory.get(ptr, buf).map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read designated chunk from the sandbox memory, consuming an appropriate amount of
|
/// Read designated chunk from the sandbox memory, consuming an appropriate amount of
|
||||||
@@ -322,7 +342,7 @@ fn read_sandbox_memory_as<E: Ext, D: Decode>(
|
|||||||
len: u32,
|
len: u32,
|
||||||
) -> Result<D, sp_sandbox::HostError> {
|
) -> Result<D, sp_sandbox::HostError> {
|
||||||
let buf = read_sandbox_memory(ctx, ptr, len)?;
|
let buf = read_sandbox_memory(ctx, ptr, len)?;
|
||||||
D::decode(&mut &buf[..]).map_err(|_| sp_sandbox::HostError)
|
D::decode(&mut &buf[..]).map_err(|_| store_err(ctx, Error::<E::T>::DecodingFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the given buffer to the designated location in the sandbox memory, consuming
|
/// Write the given buffer to the designated location in the sandbox memory, consuming
|
||||||
@@ -345,9 +365,8 @@ fn write_sandbox_memory<E: Ext>(
|
|||||||
RuntimeToken::WriteMemory(buf.len() as u32),
|
RuntimeToken::WriteMemory(buf.len() as u32),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
ctx.memory.set(ptr, buf)?;
|
ctx.memory.set(ptr, buf)
|
||||||
|
.map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the given buffer and its length to the designated locations in sandbox memory.
|
/// Write the given buffer and its length to the designated locations in sandbox memory.
|
||||||
@@ -379,7 +398,7 @@ fn write_sandbox_output<E: Ext>(
|
|||||||
let len: u32 = read_sandbox_memory_as(ctx, out_len_ptr, 4)?;
|
let len: u32 = read_sandbox_memory_as(ctx, out_len_ptr, 4)?;
|
||||||
|
|
||||||
if len < buf_len {
|
if len < buf_len {
|
||||||
Err(map_err(ctx, Error::<E::T>::OutputBufferTooSmall))?
|
Err(store_err(ctx, Error::<E::T>::OutputBufferTooSmall))?
|
||||||
}
|
}
|
||||||
|
|
||||||
charge_gas(
|
charge_gas(
|
||||||
@@ -398,7 +417,7 @@ fn write_sandbox_output<E: Ext>(
|
|||||||
/// Stores a DispatchError returned from an Ext function into the trap_reason.
|
/// Stores a DispatchError returned from an Ext function into the trap_reason.
|
||||||
///
|
///
|
||||||
/// This allows through supervisor generated errors to the caller.
|
/// This allows through supervisor generated errors to the caller.
|
||||||
fn map_err<E, Error>(ctx: &mut Runtime<E>, err: Error) -> sp_sandbox::HostError where
|
fn store_err<E, Error>(ctx: &mut Runtime<E>, err: Error) -> sp_sandbox::HostError where
|
||||||
E: Ext,
|
E: Ext,
|
||||||
Error: Into<DispatchError>,
|
Error: Into<DispatchError>,
|
||||||
{
|
{
|
||||||
@@ -406,12 +425,86 @@ fn map_err<E, Error>(ctx: &mut Runtime<E>, err: Error) -> sp_sandbox::HostError
|
|||||||
sp_sandbox::HostError
|
sp_sandbox::HostError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fallible conversion of `DispatchError` to `ReturnCode`.
|
||||||
|
fn err_into_return_code<T: Trait>(from: DispatchError) -> Result<ReturnCode, DispatchError> {
|
||||||
|
use ReturnCode::*;
|
||||||
|
|
||||||
|
let below_sub = Error::<T>::BelowSubsistenceThreshold.into();
|
||||||
|
let transfer_failed = Error::<T>::TransferFailed.into();
|
||||||
|
let not_funded = Error::<T>::NewContractNotFunded.into();
|
||||||
|
let no_code = Error::<T>::CodeNotFound.into();
|
||||||
|
let invalid_contract = Error::<T>::NotCallable.into();
|
||||||
|
|
||||||
|
match from {
|
||||||
|
x if x == below_sub => Ok(BelowSubsistenceThreshold),
|
||||||
|
x if x == transfer_failed => Ok(TransferFailed),
|
||||||
|
x if x == not_funded => Ok(NewContractNotFunded),
|
||||||
|
x if x == no_code => Ok(CodeNotFound),
|
||||||
|
x if x == invalid_contract => Ok(NotCallable),
|
||||||
|
err => Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallible conversion of a `ExecResult` to `ReturnCode`.
|
||||||
|
fn exec_into_return_code<T: Trait>(from: ExecResult) -> Result<ReturnCode, DispatchError> {
|
||||||
|
use crate::exec::ErrorOrigin::Callee;
|
||||||
|
|
||||||
|
let ExecError { error, origin } = match from {
|
||||||
|
Ok(retval) => return Ok(retval.into()),
|
||||||
|
Err(err) => err,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (error, origin) {
|
||||||
|
(_, Callee) => Ok(ReturnCode::CalleeTrapped),
|
||||||
|
(err, _) => err_into_return_code::<T>(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by Runtime API that calls into other contracts.
|
||||||
|
///
|
||||||
|
/// Those need to transform the the `ExecResult` returned from the execution into
|
||||||
|
/// a `ReturnCode`. If this conversion fails because the `ExecResult` constitutes a
|
||||||
|
/// a fatal error then this error is stored in the `ExecutionContext` so it can be
|
||||||
|
/// extracted for display in the UI.
|
||||||
|
fn map_exec_result<E: Ext>(ctx: &mut Runtime<E>, result: ExecResult)
|
||||||
|
-> Result<ReturnCode, sp_sandbox::HostError>
|
||||||
|
{
|
||||||
|
match exec_into_return_code::<E::T>(result) {
|
||||||
|
Ok(code) => Ok(code),
|
||||||
|
Err(err) => Err(store_err(ctx, err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert an error into a `ReturnCode`.
|
||||||
|
///
|
||||||
|
/// Used to decide between fatal and non-fatal errors.
|
||||||
|
fn map_dispatch_result<T, E: Ext>(ctx: &mut Runtime<E>, result: Result<T, DispatchError>)
|
||||||
|
-> Result<ReturnCode, sp_sandbox::HostError>
|
||||||
|
{
|
||||||
|
let err = if let Err(err) = result {
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
return Ok(ReturnCode::Success)
|
||||||
|
};
|
||||||
|
|
||||||
|
match err_into_return_code::<E::T>(err) {
|
||||||
|
Ok(code) => Ok(code),
|
||||||
|
Err(err) => Err(store_err(ctx, err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
// * AFTER MAKING A CHANGE MAKE SURE TO UPDATE COMPLEXITY.MD *
|
// * AFTER MAKING A CHANGE MAKE SURE TO UPDATE COMPLEXITY.MD *
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Define a function `fn init_env<E: Ext>() -> HostFunctionSet<E>` that returns
|
// Define a function `fn init_env<E: Ext>() -> HostFunctionSet<E>` that returns
|
||||||
// a function set which can be imported by an executed contract.
|
// a function set which can be imported by an executed contract.
|
||||||
|
//
|
||||||
|
// # Note
|
||||||
|
//
|
||||||
|
// Any input that leads to a out of bound error (reading or writing) or failing to decode
|
||||||
|
// data passed to the supervisor will lead to a trap. This is not documented explicitly
|
||||||
|
// for every function.
|
||||||
define_env!(Env, <E: Ext>,
|
define_env!(Env, <E: Ext>,
|
||||||
|
|
||||||
// Account for used gas. Traps if gas used is greater than gas limit.
|
// Account for used gas. Traps if gas used is greater than gas limit.
|
||||||
@@ -441,7 +534,7 @@ define_env!(Env, <E: Ext>,
|
|||||||
// - `value_ptr`: pointer into the linear memory where the value to set is placed.
|
// - `value_ptr`: pointer into the linear memory where the value to set is placed.
|
||||||
// - `value_len`: the length of the value in bytes.
|
// - `value_len`: the length of the value in bytes.
|
||||||
//
|
//
|
||||||
// # Errors
|
// # Traps
|
||||||
//
|
//
|
||||||
// - If value length exceeds the configured maximum value length of a storage entry.
|
// - If value length exceeds the configured maximum value length of a storage entry.
|
||||||
// - Upon trying to set an empty storage entry (value length is 0).
|
// - Upon trying to set an empty storage entry (value length is 0).
|
||||||
@@ -480,12 +573,7 @@ define_env!(Env, <E: Ext>,
|
|||||||
//
|
//
|
||||||
// # Errors
|
// # Errors
|
||||||
//
|
//
|
||||||
// If there is no entry under the given key then this function will return
|
// `ReturnCode::KeyNotFound`
|
||||||
// `ReturnCode::KeyNotFound`.
|
|
||||||
//
|
|
||||||
// # Traps
|
|
||||||
//
|
|
||||||
// Traps if the supplied buffer length is smaller than the size of the stored value.
|
|
||||||
ext_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
ext_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||||
let mut key: StorageKey = [0; 32];
|
let mut key: StorageKey = [0; 32];
|
||||||
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
||||||
@@ -508,24 +596,24 @@ define_env!(Env, <E: Ext>,
|
|||||||
// Should be decodable as a `T::Balance`. Traps otherwise.
|
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||||
// - value_len: length of the value buffer.
|
// - value_len: length of the value buffer.
|
||||||
//
|
//
|
||||||
// # Traps
|
// # Errors
|
||||||
//
|
//
|
||||||
// Traps if the transfer wasn't succesful. This can happen when the value transfered
|
// `ReturnCode::BelowSubsistenceThreshold`
|
||||||
// brings the sender below the existential deposit. Use `ext_terminate` to remove
|
// `ReturnCode::TransferFailed`
|
||||||
// the caller contract.
|
|
||||||
ext_transfer(
|
ext_transfer(
|
||||||
ctx,
|
ctx,
|
||||||
account_ptr: u32,
|
account_ptr: u32,
|
||||||
account_len: u32,
|
account_len: u32,
|
||||||
value_ptr: u32,
|
value_ptr: u32,
|
||||||
value_len: u32
|
value_len: u32
|
||||||
) => {
|
) -> ReturnCode => {
|
||||||
let callee: <<E as Ext>::T as frame_system::Trait>::AccountId =
|
let callee: <<E as Ext>::T as frame_system::Trait>::AccountId =
|
||||||
read_sandbox_memory_as(ctx, account_ptr, account_len)?;
|
read_sandbox_memory_as(ctx, account_ptr, account_len)?;
|
||||||
let value: BalanceOf<<E as Ext>::T> =
|
let value: BalanceOf<<E as Ext>::T> =
|
||||||
read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||||
|
|
||||||
ctx.ext.transfer(&callee, value, ctx.gas_meter).map_err(|e| map_err(ctx, e))
|
let result = ctx.ext.transfer(&callee, value, ctx.gas_meter);
|
||||||
|
map_dispatch_result(ctx, result)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Make a call to another contract.
|
// Make a call to another contract.
|
||||||
@@ -551,17 +639,14 @@ define_env!(Env, <E: Ext>,
|
|||||||
//
|
//
|
||||||
// # Errors
|
// # Errors
|
||||||
//
|
//
|
||||||
// `ReturnCode::CalleeReverted`: The callee ran to completion but decided to have its
|
// An error means that the call wasn't successful output buffer is returned unless
|
||||||
// changes reverted. The delivery of the output buffer is still possible.
|
// stated otherwise.
|
||||||
// `ReturnCode::CalleeTrapped`: The callee trapped during execution. All changes are reverted
|
|
||||||
// and no output buffer is delivered.
|
|
||||||
//
|
//
|
||||||
// # Traps
|
// `ReturnCode::CalleeReverted`: Output buffer is returned.
|
||||||
//
|
// `ReturnCode::CalleeTrapped`
|
||||||
// - Transfer of balance failed. This call can not bring the sender below the existential
|
// `ReturnCode::BelowSubsistenceThreshold`
|
||||||
// deposit. Use `ext_terminate` to remove the caller.
|
// `ReturnCode::TransferFailed`
|
||||||
// - Callee does not exist.
|
// `ReturnCode::NotCallable`
|
||||||
// - Supplied output buffer is too small.
|
|
||||||
ext_call(
|
ext_call(
|
||||||
ctx,
|
ctx,
|
||||||
callee_ptr: u32,
|
callee_ptr: u32,
|
||||||
@@ -594,22 +679,16 @@ define_env!(Env, <E: Ext>,
|
|||||||
nested_meter,
|
nested_meter,
|
||||||
input_data,
|
input_data,
|
||||||
)
|
)
|
||||||
.map_err(|_| ())
|
|
||||||
}
|
}
|
||||||
// there is not enough gas to allocate for the nested call.
|
// there is not enough gas to allocate for the nested call.
|
||||||
None => Err(()),
|
None => Err(Error::<<E as Ext>::T>::OutOfGas.into()),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
match call_outcome {
|
if let Ok(output) = &call_outcome {
|
||||||
Ok(output) => {
|
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?;
|
||||||
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?;
|
|
||||||
Ok(output.into())
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
Ok(ReturnCode::CalleeTrapped)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
map_exec_result(ctx, call_outcome)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Instantiate a contract with the specified code hash.
|
// Instantiate a contract with the specified code hash.
|
||||||
@@ -643,19 +722,18 @@ define_env!(Env, <E: Ext>,
|
|||||||
//
|
//
|
||||||
// # Errors
|
// # Errors
|
||||||
//
|
//
|
||||||
// `ReturnCode::CalleeReverted`: The callee's constructor ran to completion but decided to have
|
// Please consult the `ReturnCode` enum declaration for more information on those
|
||||||
// its changes reverted. The delivery of the output buffer is still possible but the
|
// errors. Here we only note things specific to this function.
|
||||||
// account was not created and no address is returned.
|
|
||||||
// `ReturnCode::CalleeTrapped`: The callee trapped during execution. All changes are reverted
|
|
||||||
// and no output buffer is delivered. The accounts was not created and no address is
|
|
||||||
// returned.
|
|
||||||
//
|
//
|
||||||
// # Traps
|
// An error means that the account wasn't created and no address or output buffer
|
||||||
|
// is returned unless stated otherwise.
|
||||||
//
|
//
|
||||||
// - Transfer of balance failed. This call can not bring the sender below the existential
|
// `ReturnCode::CalleeReverted`: Output buffer is returned.
|
||||||
// deposit. Use `ext_terminate` to remove the caller.
|
// `ReturnCode::CalleeTrapped`
|
||||||
// - Code hash does not exist.
|
// `ReturnCode::BelowSubsistenceThreshold`
|
||||||
// - Supplied output buffers are too small.
|
// `ReturnCode::TransferFailed`
|
||||||
|
// `ReturnCode::NewContractNotFunded`
|
||||||
|
// `ReturnCode::CodeNotFound`
|
||||||
ext_instantiate(
|
ext_instantiate(
|
||||||
ctx,
|
ctx,
|
||||||
code_hash_ptr: u32,
|
code_hash_ptr: u32,
|
||||||
@@ -690,26 +768,20 @@ define_env!(Env, <E: Ext>,
|
|||||||
nested_meter,
|
nested_meter,
|
||||||
input_data
|
input_data
|
||||||
)
|
)
|
||||||
.map_err(|_| ())
|
|
||||||
}
|
}
|
||||||
// there is not enough gas to allocate for the nested call.
|
// there is not enough gas to allocate for the nested call.
|
||||||
None => Err(()),
|
None => Err(Error::<<E as Ext>::T>::OutOfGas.into()),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
match instantiate_outcome {
|
if let Ok((address, output)) = &instantiate_outcome {
|
||||||
Ok((address, output)) => {
|
if !output.flags.contains(ReturnFlags::REVERT) {
|
||||||
if !output.flags.contains(ReturnFlags::REVERT) {
|
write_sandbox_output(
|
||||||
write_sandbox_output(
|
ctx, address_ptr, address_len_ptr, &address.encode(), true
|
||||||
ctx, address_ptr, address_len_ptr, &address.encode(), true
|
)?;
|
||||||
)?;
|
}
|
||||||
}
|
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?;
|
||||||
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?;
|
|
||||||
Ok(output.into())
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
Ok(ReturnCode::CalleeTrapped)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
map_exec_result(ctx, instantiate_outcome.map(|(_id, retval)| retval))
|
||||||
},
|
},
|
||||||
|
|
||||||
// Remove the calling account and transfer remaining balance.
|
// Remove the calling account and transfer remaining balance.
|
||||||
@@ -722,6 +794,10 @@ define_env!(Env, <E: Ext>,
|
|||||||
// where all remaining funds of the caller are transfered.
|
// where all remaining funds of the caller are transfered.
|
||||||
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
||||||
// - beneficiary_len: length of the address buffer.
|
// - beneficiary_len: length of the address buffer.
|
||||||
|
//
|
||||||
|
// # Traps
|
||||||
|
//
|
||||||
|
// - The contract is live i.e is already on the call stack.
|
||||||
ext_terminate(
|
ext_terminate(
|
||||||
ctx,
|
ctx,
|
||||||
beneficiary_ptr: u32,
|
beneficiary_ptr: u32,
|
||||||
@@ -939,6 +1015,11 @@ define_env!(Env, <E: Ext>,
|
|||||||
// encodes the rent allowance that must be set in the case of successful restoration.
|
// encodes the rent allowance that must be set in the case of successful restoration.
|
||||||
// `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys
|
// `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys
|
||||||
// laid out sequentially.
|
// laid out sequentially.
|
||||||
|
//
|
||||||
|
// # Traps
|
||||||
|
//
|
||||||
|
// - Tombstone hashes do not match
|
||||||
|
// - Calling cantract is live i.e is already on the call stack.
|
||||||
ext_restore_to(
|
ext_restore_to(
|
||||||
ctx,
|
ctx,
|
||||||
dest_ptr: u32,
|
dest_ptr: u32,
|
||||||
|
|||||||
Reference in New Issue
Block a user