seal: Rework contracts API (#6573)

* Transition getter functions to not use scratch buffer

* Remove scratch buffer from ext_get_storage

* Remove scratch buffer from ext_call

* Remove scratch buffer from ext_instantiate

* Add ext_input and remove scratch buffer

* Rework error handling (changes RPC exposed data)

* ext_return passes a flags field instead of a return code
	* Flags is only for seal and not for the caller
	* flags: u32 replaced status_code: u8 in RPC exposed type
* API functions use a unified error type (ReturnCode)
* ext_transfer now traps on error to be consistent with call and instantiate

* Remove the no longer used `Dispatched` event

* Updated inline documentation

* Prevent skipping of copying the output for getter API

* Return gas_consumed from the RPC contracts call interface

* Updated COMPLEXTITY.md

* Rename ext_gas_price to ext_weight_to_fee

* Align comments with spaces

* Removed no longer used `ExecError`

* Remove possible panic in `from_typed_value`

* Use a struct as associated data for SpecialTrap::Return

* Fix nits in COMPLEXITY.md

* Renamed SpecialTrap to TrapReason

* Fix test

* Finish renaming special_trap -> trap_reason

* Remove no longer used get_runtime_storage

* fixup! Remove no longer used get_runtime_storage

* Removed tabs for comment aligment
This commit is contained in:
Alexander Theißen
2020-07-09 15:07:02 +02:00
committed by GitHub
parent a4427f3635
commit 25de5b5c78
26 changed files with 1116 additions and 1256 deletions
+1
View File
@@ -4178,6 +4178,7 @@ name = "pallet-contracts"
version = "2.0.0-rc4"
dependencies = [
"assert_matches",
"bitflags",
"frame-support",
"frame-system",
"hex-literal",
+16 -13
View File
@@ -491,32 +491,31 @@ const CODE_TRANSFER: &str = r#"
;; value_ptr: u32,
;; value_len: u32,
;; input_data_ptr: u32,
;; input_data_len: u32
;; input_data_len: u32,
;; output_ptr: u32,
;; output_len_ptr: u32
;; ) -> u32
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (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_input" (func $ext_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "deploy")
)
(func (export "call")
(block $fail
;; load and check the input data (which is stored in the scratch buffer).
;; Load input data to contract memory
(call $ext_input
(i32.const 0)
(i32.const 52)
)
;; fail if the input size is not != 4
(br_if $fail
(i32.ne
(i32.const 4)
(call $ext_scratch_size)
(i32.load (i32.const 52))
)
)
(call $ext_scratch_read
(i32.const 0)
(i32.const 0)
(i32.const 4)
)
(br_if $fail
(i32.ne
(i32.load8_u (i32.const 0))
@@ -551,6 +550,8 @@ const CODE_TRANSFER: &str = r#"
(i32.const 16) ;; Length of the buffer with value to transfer.
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
)
@@ -571,6 +572,8 @@ const CODE_TRANSFER: &str = r#"
"\06\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
"\00\00"
)
;; Length of the input buffer
(data (i32.const 52) "\04")
)
"#;
+3 -2
View File
@@ -1066,12 +1066,13 @@ impl_runtime_apis! {
gas_limit: u64,
input_data: Vec<u8>,
) -> ContractExecResult {
let exec_result =
let (exec_result, gas_consumed) =
Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data);
match exec_result {
Ok(v) => ContractExecResult::Success {
status: v.status,
flags: v.flags.bits(),
data: v.data,
gas_consumed: gas_consumed,
},
Err(_) => ContractExecResult::Error,
}
+100 -128
View File
@@ -4,19 +4,19 @@ This analysis is on the computing and memory complexity of specific procedures.
The primary goal is to come up with decent pricing for functions that can be invoked by a user (via extrinsics) or by untrusted code that prevents DoS attacks.
# Sandboxing
## Sandboxing
It makes sense to describe the sandboxing module first because the smart-contract module is built upon it.
## Memory
### Memory
### set
#### set
Copies data from the supervisor's memory to the guest's memory.
**complexity**: It doesn't allocate, and the computational complexity is proportional to the number of bytes to copy.
### get
#### get
Copies data from the guest's memory to the supervisor's memory.
@@ -78,17 +78,10 @@ The size of the arguments and the return value depends on the exact function in
**complexity**: Memory and computational complexity can be considered as a constant.
# `AccountDb`
## Transactional Storage
`AccountDb` is an abstraction that supports collecting changes to accounts with the ability to efficiently reverting them. Contract
execution contexts operate on the AccountDb. All changes are flushed into underlying storage only after origin transaction succeeds.
## Relation to the underlying storage
At present, `AccountDb` is implemented as a cascade of overlays with the direct storage at the bottom. The direct
storage `AccountDb` leverages child tries. Each overlay is represented by a `Map`. On a commit from an overlay to an
overlay, maps are merged. On commit from an overlay to the bottommost `AccountDb` all changes are flushed to the storage
and on revert, the overlay is just discarded.
The contracts module makes use of the nested storage transactions feature offered by
the underlying storage which allows efficient roll back of changes made by contracts.
> ️ The underlying storage has a overlay layer implemented as a `Map`. If the runtime reads a storage location and the
> respective key doesn't exist in the overlay, then the underlying storage performs a DB access, but the value won't be
@@ -105,23 +98,24 @@ storage access.
## get_storage, get_code_hash, get_rent_allowance, get_balance, contract_exists
These functions check the local cache for a requested value and, if it is there, the value is returned. Otherwise, these functions will ask an underlying `AccountDb` for the value. This means that the number of lookups is proportional to the depth of the overlay cascade. If the value can't be found before reaching the bottommost `AccountDb`, then a DB read will be performed (in case `get_balance` the function `free_balance` will be invoked).
Those query the underlying storage for the requested value. If the value was modified in the
current block they are served from the cache. Otherwise a database read is performed.
A lookup in the local cache consists of at least one `Map` lookup, for locating the specific account. For `get_storage` there is a second lookup: because account's storage is implemented as a nested map, another lookup is required for fetching a storage value by a key.
These functions return an owned value as its result, so memory usage depends on the value being returned.
**complexity**: The memory complexity is proportional to the size of the value. The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though.
**complexity**: The memory complexity is proportional to the size of the value. The computational complexity is proportional the size of the value; the cost is dominated by the DB read.
## set_storage, set_balance, set_rent_allowance
These functions only modify the local `Map`.
These function write to the underlying storage which caches those values and does not write
them to the database immediately.
A lookup in the local cache consists of at least one `Map` lookup, for locating the specific account. For `get_storage` there is a second lookup: because account's storage is implemented as a nested map, another lookup is required for fetching a storage value by a key.
While these functions only modify the local cache, they trigger a database write later when
all changes that were not rolled back are written to storage. Moreover, if the balance of the
account is changed to be below `existential_deposit` then that account along with all its storage
will be removed, which requires time proportional to the number of storage entries that account has.
It should be ensured that pricing accounts for these facts.
While these functions only modify the local `Map`, if changes made by them are committed to the bottommost `AccountDb`, each changed entry in the `Map` will require a DB write. Moreover, if the balance of the account is changed to be below `existential_deposit` then that account along with all its storage will be removed, which requires time proportional to the number of storage entries that account has. It should be ensured that pricing accounts for these facts.
**complexity**: Each lookup has a logarithmical computing time to the number of already inserted entries. No additional memory is required.
**complexity**: Each lookup has a logarithmical computing time to the number of already inserted entries.
No additional memory is required.
## instantiate_contract
@@ -131,9 +125,11 @@ Calls `contract_exists` and if it doesn't exist, do not modify the local `Map` s
## commit
In this function, all cached values will be inserted into the underlying `AccountDb` or into the storage.
In this function, all values modified in the current transactions are committed to the parent
transaction.
We are doing `N` inserts into `Map` (`O(log M)` complexity) or into the storage, where `N` is the size of the committed `Map` and `M` is the size of the map of the underlying overlay. Consider adjusting the price of modifying the `AccountDb` to account for this (since pricing for the count of entries in `commit` will make the price of commit way less predictable). No additional memory is required.
This will trigger `N` inserts into parent transaction (`O(log M)` complexity) or into the storage, where `N` is the size of the current transaction and `M` is the size of the parent transaction. Consider adjusting the price of modifying the
current transaction to account for this (since pricing for the count of entries in `commit` will make the price of commit way less predictable). No additional memory is required.
Note that in case of storage modification we need to construct a key in the underlying storage. In order to do that we need:
@@ -143,21 +139,21 @@ Note that in case of storage modification we need to construct a key in the unde
There is also a special case to think of: if the balance of some account goes below `existential_deposit`, then all storage entries of that account will be erased, which requires time proportional to the number of storage entries that account has.
**complexity**: `N` inserts into a `Map` or eventually into the storage (if committed). Every deleted account will induce removal of all its storage which is proportional to the number of storage entries that account has.
**complexity**: `N` inserts into a transaction or eventually into the storage (if committed). Every deleted account will induce removal of all its storage which is proportional to the number of storage entries that account has.
## revert
Consists of dropping (in the Rust sense) of the `AccountDb`.
Consists of dropping (in the Rust sense) of the current transaction.
**complexity**: Computing complexity is proportional to a number of changed entries in a overlay. No additional memory is required.
# Executive
## Executive
## Transfer
### Transfer
This function performs the following steps:
1. Querying source and destination balances from an overlay (see `get_balance`),
1. Querying source and destination balances from the current transaction (see `get_balance`),
2. Querying `existential_deposit`.
3. Executing `ensure_account_liquid` hook.
4. Updating source and destination balance in the overlay (see `set_balance`).
@@ -171,9 +167,9 @@ returns with an error.
Assuming marshaled size of a balance value is of the constant size we can neglect its effect on the performance.
**complexity**: up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. For the current `AccountDb` implementation computing complexity also depends on the depth of the `AccountDb` cascade. Memorywise it can be assumed to be constant.
**complexity**: up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. Memorywise it can be assumed to be constant.
## Initialization
### Initialization
Before a call or instantiate can be performed the execution context must be initialized.
@@ -188,7 +184,7 @@ implementation they just involve a DB read.
For subsequent calls and instantiations during contract execution, the initialization requires no
expensive operations.
## Terminate
### Terminate
This function performs the following steps:
@@ -204,17 +200,17 @@ the call stack is of a fixed maximum size we consider this operation as constant
we are using child trie removal which is linear in the amount of stored keys. Upcoming changes
will make the account removal constant time.
## Call
### Call
This function receives input data for the contract execution. The execution consists of the following steps:
1. Initialization of the execution context.
2. Checking rent payment.
3. Loading code from the DB.
4. `transfer`-ing funds between the caller and the destination account.
5. Executing the code of the destination account.
6. Committing overlayed changed to the underlying `AccountDb`.
4. Starting a new storage transaction.
5. `transfer`-ing funds between the caller and the destination account.
6. Executing the code of the destination account.
7. Committing or rolling back the storage transaction.
**Note** that the complexity of executing the contract code should be considered separately.
@@ -235,22 +231,24 @@ Loading code most likely will trigger a DB read, since the code is immutable and
Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
Finally, all changes are `commit`-ted into the underlying overlay. The complexity of this depends on the number of changes performed by the code. Thus, the pricing of storage modification should account for that.
Finally, the current storage transaction is closed. The complexity of this depends on the number of changes performed by the code. Thus, the pricing of storage modification should account for that.
**complexity**:
- Only for the first invocation of the contract: up to 5 DB reads and one DB write as well as logic executed by `ensure_can_withdraw`, `withdraw`, `make_free_balance_be`.
- On top of that for every invocation: Up to 5 DB reads. DB read of the code is of dynamic size. There can also be up to 2 DB writes (if flushed to the storage). Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
## Instantiate
### Instantiate
This function takes the code of the constructor and input data. Instantiation of a contract consists of the following steps:
1. Initialization of the execution context.
2. Calling `DetermineContractAddress` hook to determine an address for the contract,
3. `transfer`-ing funds between self and the newly instantiated contract.
4. Executing the constructor code. This will yield the final code of the code.
5. Storing the code for the newly instantiated contract in the overlay.
6. Committing overlayed changed to the underlying `AccountDb`.
3. Starting a new storage transaction.
4. `transfer`-ing funds between self and the newly instantiated contract.
5. Executing the constructor code. This will yield the final code of the code.
6. Storing the code for the newly instantiated contract in the overlay.
7. Committing or rolling back the storage transaction.
**Note** that the complexity of executing the constructor code should be considered separately.
@@ -262,19 +260,43 @@ Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to
Storing the code in the overlay may induce another DB write (if flushed to the storage) with the size proportional to the size of the constructor code.
Finally, all changes are `commit`-ted into the underlying overlay. The complexity of this depends on the number of changes performed by the constructor code. Thus, the pricing of storage modification should account for that.
Finally, the current storage transaction is closed.. The complexity of this depends on the number of changes performed by the constructor code. Thus, the pricing of storage modification should account for that.
**complexity**: Up to 2 DB reads and induces up to 3 DB writes (if flushed to the storage), one of which is dependent on the size of the code. Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
# Externalities
## Contracts API
Each external function invoked from a contract can involve some overhead.
Each API function invoked from a contract can involve some overhead.
## ext_gas
## Getter functions
**complexity**: This is of constant complexity.
Those are simple getter functions which copy a requested value to contract memory. They
all have the following two arguments:
## ext_set_storage
- `output_ptr`: Pointer into contract memory where to copy the value.
- `output_len_ptr`: Pointer into contract memory where the size of the buffer is stored. The size of the copied value is also stored there.
**complexity**: The size of the returned value is constant for a given runtime. Therefore we
consider its complexity constant even though some of them might involve at most one DB read. Some of those
functions call into other pallets of the runtime. The assumption here is that those functions are also
linear in regard to the size of the data that is returned and therefore considered constant for a
given runtime.
This is the list of getters:
- ext_caller
- ext_address
- ext_weight_to_fee
- ext_gas_left
- ext_balance
- ext_value_transferred
- ext_now
- ext_minimum_balance
- ext_tombstone_deposit
- ext_rent_allowance
- ext_block_number
### ext_set_storage
This function receives a `key` and `value` as arguments. It consists of the following steps:
@@ -283,7 +305,7 @@ This function receives a `key` and `value` as arguments. It consists of the foll
**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly.
## ext_clear_storage
### ext_clear_storage
This function receives a `key` as argument. It consists of the following steps:
@@ -293,23 +315,22 @@ This function receives a `key` as argument. It consists of the following steps:
**complexity**: Complexity is constant. This function induces a DB write to clear the storage entry
(upon being flushed to the storage) and should be priced accordingly.
## ext_get_storage
### ext_get_storage
This function receives a `key` as an argument. It consists of the following steps:
1. Reading the sandbox memory for `key` (see sandboxing memory get).
2. Reading the storage with the given key (see `get_storage`). It receives back the owned result buffer.
3. Replacing the scratch buffer.
3. Writing the storage value to contract memory.
Key is of a constant size. Therefore, the sandbox memory load can be considered to be of constant complexity.
Unless the value is cached, a DB read will be performed. The size of the value is not known until the read is
performed. Moreover, the DB read has to be synchronous and no progress can be made until the value is fetched.
**complexity**: The memory and computing complexity is proportional to the size of the fetched value. This function performs a
DB read.
**complexity**: The memory and computing complexity is proportional to the size of the fetched value. This function performs a DB read.
## ext_transfer
### ext_transfer
This function receives the following arguments:
@@ -320,18 +341,19 @@ It consists of the following steps:
1. Loading `account` buffer from the sandbox memory (see sandboxing memory get) and then decoding it.
2. Loading `value` buffer from the sandbox memory and then decoding it.
4. Invoking the executive function `transfer`.
3. Invoking the executive function `transfer`.
Loading of `account` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`.
## ext_call
### ext_call
This function receives the following arguments:
- `callee` buffer of a marshaled `AccountId`,
- `gas` limit which is plain u64,
- `value` buffer of a marshaled `Balance`,
- `input_data`. An arbitrarily sized byte vector.
- `input_data` an arbitrarily sized byte vector.
- `output_ptr` pointer to contract memory.
It consists of the following steps:
@@ -339,14 +361,15 @@ It consists of the following steps:
2. Loading `value` buffer from the sandbox memory and then decoding it.
3. Loading `input_data` buffer from the sandbox memory.
4. Invoking the executive function `call`.
5. Writing output buffer to contract memory.
Loading of `callee` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`.
Loading `input_data` should be charged in any case.
**complexity**: All complexity comes from loading buffers and executing `call` executive function. The former component is proportional to the sizes of `callee`, `value` and `input_data` buffers. The latter component completely depends on the complexity of `call` executive function, and also dominated by it.
**complexity**: All complexity comes from loading and writing buffers and executing `call` executive function. The former component is proportional to the sizes of `callee`, `value`, `input_data` and `output_ptr` buffers. The latter component completely depends on the complexity of `call` executive function, and also dominated by it.
## ext_instantiate
### ext_instantiate
This function receives the following arguments:
@@ -368,7 +391,7 @@ Loading `init_code` and `input_data` should be charged in any case.
**complexity**: All complexity comes from loading buffers and executing `instantiate` executive function. The former component is proportional to the sizes of `init_code`, `value` and `input_data` buffers. The latter component completely depends on the complexity of `instantiate` executive function and also dominated by it.
## ext_terminate
### ext_terminate
This function receives the following arguments:
@@ -382,16 +405,23 @@ Loading of the `beneficiary` buffer should be charged. This is because the sizes
**complexity**: All complexity comes from loading buffers and executing `terminate` executive function. The former component is proportional to the size of the `beneficiary` buffer. The latter component completely depends on the complexity of `terminate` executive function and also dominated by it.
## ext_return
### ext_input
This function receives a `data` buffer as an argument. Execution of the function consists of the following steps:
This function receives a pointer to contract memory. It copies the input to the contract call to this location.
1. Loading `data` buffer from the sandbox memory (see sandboxing memory get),
2. Trapping
**complexity**: The complextity is proportional to the size of the input buffer.
### ext_return
This function receives a `data` buffer and `flags` arguments. Execution of the function consists of the following steps:
1. Loading `data` buffer from the sandbox memory (see sandboxing memory get).
2. Storing the `u32` flags value.
3. Trapping
**complexity**: The complexity of this function is proportional to the size of the `data` buffer.
## ext_deposit_event
### ext_deposit_event
This function receives a `data` buffer as an argument. Execution of the function consists of the following steps:
@@ -402,49 +432,7 @@ This function receives a `data` buffer as an argument. Execution of the function
**complexity**: The complexity of this function is proportional to the size of the `data` buffer.
## ext_caller
This function serializes the address of the caller into the scratch buffer.
**complexity**: Assuming that the address is of constant size, this function has constant complexity.
## ext_random
This function serializes a random number generated by the given subject into the scratch buffer.
The complexity of this function highly depends on the complexity of `System::random`. `max_subject_len`
limits the size of the subject buffer.
**complexity**: The complexity of this function depends on the implementation of `System::random`.
## ext_now
This function serializes the current block's timestamp into the scratch buffer.
**complexity**: Assuming that the timestamp is of constant size, this function has constant complexity.
## ext_scratch_size
This function returns the size of the scratch buffer.
**complexity**: This function is of constant complexity.
## ext_scratch_read
This function copies slice of data from the scratch buffer to the sandbox memory. The calling code specifies the slice length. Execution of the function consists of the following steps:
1. Storing a specified slice of the scratch buffer into the sandbox memory (see sandboxing memory set)
**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required.
## ext_scratch_write
This function copies slice of data from the sandbox memory to the scratch buffer. The calling code specifies the slice length. Execution of the function consists of the following steps:
1. Loading a slice from the sandbox memory into the (see sandboxing memory get)
**complexity**: Complexity is proportional to the length of the slice.
## ext_set_rent_allowance
### ext_set_rent_allowance
This function receives the following argument:
@@ -457,22 +445,6 @@ It consists of the following steps:
**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly.
## ext_rent_allowance
It consists of the following steps:
1. Invoking `get_rent_allowance` AccountDB function.
2. Serializing the rent allowance of the current contract into the scratch buffer.
**complexity**: Assuming that the rent allowance is of constant size, this function has constant complexity. This
function performs a DB read.
## ext_block_number
This function serializes the current block's number into the scratch buffer.
**complexity**: Assuming that the block number is of constant size, this function has constant complexity.
## Built-in hashing functions
This paragraph concerns the following supported built-in hash functions:
+1
View File
@@ -25,6 +25,7 @@ sp-sandbox = { version = "0.8.0-rc4", default-features = false, path = "../../pr
frame-support = { version = "2.0.0-rc4", default-features = false, path = "../support" }
frame-system = { version = "2.0.0-rc4", default-features = false, path = "../system" }
pallet-contracts-primitives = { version = "2.0.0-rc4", default-features = false, path = "common" }
bitflags = "1.0"
[dev-dependencies]
wabt = "0.9.2"
@@ -1,9 +1,8 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_balance" (func $ext_balance))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_input" (func $ext_input (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_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
(import "env" "ext_println" (func $ext_println (param i32 i32)))
(import "env" "memory" (memory 1 1))
@@ -17,15 +16,17 @@
)
(func $current_balance (param $sp i32) (result i64)
(call $ext_balance)
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 8))
)
(call $ext_scratch_read
(i32.sub (get_local $sp) (i32.const 8))
(i32.const 0)
(i32.store
(i32.sub (get_local $sp) (i32.const 16))
(i32.const 8)
)
(call $ext_balance
(i32.sub (get_local $sp) (i32.const 8))
(i32.sub (get_local $sp) (i32.const 16))
)
(call $assert
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 16))) (i32.const 8))
)
(i64.load (i32.sub (get_local $sp) (i32.const 8)))
)
@@ -36,21 +37,20 @@
(local $exit_code i32)
(local $balance i64)
;; Length of the buffer
(i32.store (i32.const 20) (i32.const 32))
;; Copy input to this contracts memory
(call $ext_input (i32.const 24) (i32.const 20))
;; Input data is the code hash of the contract to be deployed.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 20))
(i32.const 32)
)
)
;; Copy code hash from scratch buffer into this contract's memory.
(call $ext_scratch_read
(i32.const 24) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 32) ;; Count of bytes to copy.
)
;; Read current balance into local variable.
(set_local $sp (i32.const 1024))
(set_local $balance
@@ -67,17 +67,16 @@
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 9) ;; Pointer to input data buffer address
(i32.const 7) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max sentinel value: do not copy address
(i32.const 0) ;; Length is ignored in this case
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
)
;; Check non-zero exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x11))
)
;; Check that scratch buffer is empty since contract instantiation failed.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 0))
(i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
)
;; Check that balance has not changed.
@@ -95,17 +94,16 @@
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max sentinel value: do not copy address
(i32.const 0) ;; Length is ignored in this case
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
)
;; Check for special trap exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x0100))
)
;; Check that scratch buffer is empty since contract instantiation failed.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 0))
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
)
;; Check that balance has not changed.
@@ -113,6 +111,12 @@
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Length of the output buffer
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 8)
)
;; Deploy the contract successfully.
(set_local $exit_code
(call $ext_instantiate
@@ -123,24 +127,22 @@
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
(i32.const 16) ;; Pointer to the address output buffer
(i32.sub (get_local $sp) (i32.const 4)) ;; Pointer to the address buffer length
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
)
;; Check for success exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x00))
(i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success
)
;; Check that scratch buffer contains the address of the new contract.
;; Check that address has the expected length
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 8))
)
;; Copy contract address from scratch buffer into this contract's memory.
(call $ext_scratch_read
(i32.const 16) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 4))) (i32.const 8))
)
;; Check that balance has been deducted.
@@ -151,6 +153,18 @@
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Zero out destination buffer of output
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
)
;; Length of the output buffer
(i32.store
(i32.sub (get_local $sp) (i32.const 8))
(i32.const 4)
)
;; Call the new contract and expect it to return failing exit code.
(set_local $exit_code
(call $ext_call
@@ -161,26 +175,19 @@
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 9) ;; Pointer to input data buffer address
(i32.const 7) ;; Length of input data buffer
(i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer
(i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len
)
)
;; Check non-zero exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x11))
(i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
)
;; Check that scratch buffer contains the expected return data.
;; Check that output buffer contains the expected return data.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 3))
)
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
)
(call $ext_scratch_read
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
(i32.const 3)
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 8))) (i32.const 3))
)
(call $assert
(i32.eq
@@ -204,17 +211,14 @@
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this cas
)
)
;; Check for special trap exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x0100))
)
;; Check that scratch buffer is empty since call trapped.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 0))
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
)
;; Check that balance has not changed.
@@ -222,6 +226,18 @@
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Zero out destination buffer of output
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
)
;; Length of the output buffer
(i32.store
(i32.sub (get_local $sp) (i32.const 8))
(i32.const 4)
)
;; Call the contract successfully.
(set_local $exit_code
(call $ext_call
@@ -232,26 +248,19 @@
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
(i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer
(i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len
)
)
;; Check for success exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x00))
(i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success
)
;; Check that scratch buffer contains the expected return data.
;; Check that the output buffer contains the expected return data.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 4))
)
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
)
(call $ext_scratch_read
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
(i32.const 4)
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 8))) (i32.const 4))
)
(call $assert
(i32.eq
@@ -271,5 +280,5 @@
(data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.
;; Chosen to be greater than existential deposit.
(data (i32.const 8) "\00\11\22\33\44\55\66\77") ;; The input data to instantiations and calls.
(data (i32.const 8) "\00\01\22\33\44\55\66\77") ;; The input data to instantiations and calls.
)
@@ -1,9 +1,14 @@
(module
(import "env" "ext_rent_allowance" (func $ext_rent_allowance))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_rent_allowance" (func $ext_rent_allowance (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) reserved for $ext_rent_allowance output
;; [8, 16) length of the buffer
(data (i32.const 8) "\08")
;; [16, inf) zero initialized
(func $assert (param i32)
(block $ok
(br_if $ok
@@ -16,30 +21,21 @@
(func (export "call"))
(func (export "deploy")
;; fill the scratch buffer with the rent allowance.
(call $ext_rent_allowance)
;; fill the buffer with the rent allowance.
(call $ext_rent_allowance (i32.const 0) (i32.const 8))
;; assert $ext_scratch_size == 8
;; assert len == 8
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 8))
(i32.const 8)
)
)
;; copy contents of the scratch buffer into the contract's memory.
(call $ext_scratch_read
(i32.const 8) ;; Pointer in memory to the place where to copy.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; assert that contents of the buffer is equal to <BalanceOf<T>>::max_value().
(call $assert
(i64.eq
(i64.load
(i32.const 8)
)
(i64.load (i32.const 0))
(i64.const 0xFFFFFFFFFFFFFFFF)
)
)
@@ -1,7 +1,6 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
(import "env" "ext_input" (func $ext_input (param i32 i32)))
(import "env" "ext_return" (func $ext_return (param i32 i32 i32)))
(import "env" "ext_hash_sha2_256" (func $ext_hash_sha2_256 (param i32 i32 i32)))
(import "env" "ext_hash_keccak_256" (func $ext_hash_keccak_256 (param i32 i32 i32)))
@@ -25,8 +24,7 @@
;; Called by the tests.
;;
;; The `call` function expects data in a certain format in the scratch
;; buffer.
;; The `call` function expects data in a certain format in the input buffer.
;;
;; 1. The first byte encodes an identifier for the crypto hash function
;; under test. (*)
@@ -34,7 +32,7 @@
;; crypto hash function chosen in 1.
;;
;; The `deploy` function then computes the chosen crypto hash function
;; given the input and puts the result back into the scratch buffer.
;; given the input and puts the result into the output buffer.
;; After contract execution the test driver then asserts that the returned
;; values are equal to the expected bytes for the input and chosen hash
;; function.
@@ -48,33 +46,36 @@
;; | 2 | BLAKE2 | 256 |
;; | 3 | BLAKE2 | 128 |
;; ---------------------------------
(func (export "call") (result i32)
(func (export "call")
(local $chosen_hash_fn i32)
(local $input_len_ptr i32)
(local $input_ptr i32)
(local $input_len i32)
(local $output_ptr i32)
(local $output_len i32)
(local.set $input_len_ptr (i32.const 256))
(local.set $input_ptr (i32.const 10))
(call $ext_scratch_read (local.get $input_ptr) (i32.const 0) (call $ext_scratch_size))
(i32.store (local.get $input_len_ptr) (i32.const 246))
(call $ext_input (local.get $input_ptr) (local.get $input_len_ptr))
(local.set $chosen_hash_fn (i32.load8_u (local.get $input_ptr)))
(if (i32.gt_u (local.get $chosen_hash_fn) (i32.const 7))
;; We check that the chosen hash fn identifier is within bounds: [0,7]
(unreachable)
)
(local.set $input_ptr (i32.add (local.get $input_ptr) (i32.const 1)))
(local.set $input_len (i32.sub (call $ext_scratch_size) (i32.const 1)))
(local.set $output_ptr (i32.const 100))
(local.set $input_len (i32.sub (i32.load (local.get $input_len_ptr)) (i32.const 1)))
(local.set $output_len (i32.load8_u (local.get $chosen_hash_fn)))
(call_indirect (type $hash_fn_sig)
(local.get $input_ptr)
(local.get $input_len)
(local.get $output_ptr)
(local.get $input_ptr)
(local.get $chosen_hash_fn) ;; Which crypto hash function to execute.
)
(call $ext_scratch_write
(local.get $output_ptr) ;; Linear memory location of the output buffer.
(call $ext_return
(i32.const 0)
(local.get $input_ptr) ;; Linear memory location of the output buffer.
(local.get $output_len) ;; Number of output buffer bytes.
)
(i32.const 0)
(unreachable)
)
)
@@ -1,12 +1,28 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32)))
(import "env" "ext_input" (func $ext_input (param i32 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_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 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_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) Endowment to send when creating contract.
(data (i32.const 0) "\00\00\01")
;; [8, 16) Value to send when calling contract.
;; [16, 48) The key to store the contract address under.
;; [48, 80) Buffer where to store the input to the contract
;; [80, 88) Buffer where to store the address of the instantiated contract
;; [88, 96) Size of the buffer
(data (i32.const 88) "\08")
;; [96, 100) Size of the input buffer
(data (i32.const 96) "\20")
(func $assert (param i32)
(block $ok
(br_if $ok
@@ -17,21 +33,15 @@
)
(func (export "deploy")
;; Input data is the code hash of the contract to be deployed.
;; Input data is the code hash of the contract to be deployed.
(call $ext_input (i32.const 48) (i32.const 96))
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 96))
(i32.const 32)
)
)
;; Copy code hash from scratch buffer into this contract's memory.
(call $ext_scratch_read
(i32.const 48) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 32) ;; Count of bytes to copy.
)
;; Deploy the contract with the provided code hash.
(call $assert
(i32.eq
@@ -43,23 +53,22 @@
(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 80) ;; Buffer where to store address of new contract
(i32.const 88) ;; Pointer to the length of the buffer
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this cas
)
(i32.const 0)
)
)
;; Read the address of the instantiated contract into memory.
;; Check that address has expected length
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 88))
(i32.const 8)
)
)
(call $ext_scratch_read
(i32.const 80) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; Store the return address.
(call $ext_set_storage
@@ -75,21 +84,18 @@
(i32.eq
(call $ext_get_storage
(i32.const 16) ;; Pointer to the key
(i32.const 80) ;; Pointer to the value
(i32.const 88) ;; Pointer to the len of the value
)
(i32.const 0)
)
)
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 88))
(i32.const 8)
)
)
(call $ext_scratch_read
(i32.const 80) ;; The pointer where to store the contract address.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; Calling the destination contract with non-empty input data should fail.
(call $assert
@@ -102,8 +108,11 @@
(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 0x0100)
(i32.const 0x1)
)
)
@@ -118,6 +127,8 @@
(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)
)
@@ -136,13 +147,11 @@
(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)
)
)
)
(data (i32.const 0) "\00\00\01") ;; Endowment to send when creating contract.
(data (i32.const 8) "") ;; Value to send when calling contract.
(data (i32.const 16) "") ;; The key to store the contract address under.
)
+15 -15
View File
@@ -1,10 +1,15 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_balance" (func $ext_balance))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result 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" "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)
(block $ok
(br_if $ok
@@ -18,34 +23,29 @@
(func (export "call")
;; Send entire remaining balance to the 0 address.
(call $ext_balance)
(call $ext_balance (i32.const 0) (i32.const 8))
;; Balance should be encoded as a u64.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 8))
(i32.const 8)
)
)
;; Read balance into memory.
(call $ext_scratch_read
(i32.const 8) ;; Pointer to write balance to
(i32.const 0) ;; Offset into scratch buffer
(i32.const 8) ;; Length of encoded balance
)
;; Self-destruct by sending full balance to the 0 address.
(call $assert
(i32.eq
(call $ext_call
(i32.const 0) ;; Pointer to 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 8) ;; 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 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,6 @@
(module
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
(import "env" "ext_input" (func $ext_input (param i32 i32)))
(import "env" "ext_restore_to"
(func $ext_restore_to
(param i32 i32 i32 i32 i32 i32 i32 i32)
@@ -7,7 +8,25 @@
)
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "call")
;; copy code hash to contract memory
(call $ext_input (i32.const 264) (i32.const 304))
(call $assert
(i32.eq
(i32.load (i32.const 304))
(i32.const 32)
)
)
(call $ext_restore_to
;; Pointer and length of the encoded dest buffer.
(i32.const 256)
@@ -49,12 +68,11 @@
;; Address of bob
(data (i32.const 256) "\02\00\00\00\00\00\00\00")
;; Code hash of SET_RENT
(data (i32.const 264)
"\ab\d6\58\65\1e\83\6e\4a\18\0d\f2\6d\bc\42\ba\e9"
"\3d\64\76\e5\30\5b\33\46\bb\4d\43\99\38\21\ee\32"
)
;; [264, 296) Code hash of SET_RENT (copied here by ext_input)
;; Rent allowance
;; [296, 304) Rent allowance
(data (i32.const 296) "\32\00\00\00\00\00\00\00")
;; [304, 308) Size of SET_RENT buffer
(data (i32.const 304) "\20")
)
@@ -1,5 +1,5 @@
(module
(import "env" "ext_return" (func $ext_return (param i32 i32)))
(import "env" "ext_return" (func $ext_return (param i32 i32 i32)))
(import "env" "ext_deposit_event" (func $ext_deposit_event (param i32 i32 i32 i32)))
(import "env" "memory" (memory 1 1))
@@ -12,6 +12,7 @@
(i32.const 4) ;; The data buffer's length
)
(call $ext_return
(i32.const 0)
(i32.const 8)
(i32.const 4)
)
@@ -1,39 +1,33 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
(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))
;; [0, 128) buffer where input is copied
;; [128, 132) length of the input buffer
(data (i32.const 128) "\80")
;; Deploy routine is the same as call.
(func (export "deploy") (result i32)
(func (export "deploy")
(call $call)
)
;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data.
(func $call (export "call") (result i32)
(local $buf_size i32)
(local $exit_status i32)
;; Find out the size of the scratch buffer
(set_local $buf_size (call $ext_scratch_size))
;; Copy scratch buffer into this contract memory.
(call $ext_scratch_read
(i32.const 0) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(get_local $buf_size) ;; Count of bytes to copy.
)
(func $call (export "call")
;; Copy input into this contracts memory.
(call $ext_input (i32.const 0) (i32.const 128))
;; Copy all but the first 4 bytes of the input data as the output data.
(call $ext_scratch_write
;; Use the first byte as exit status
(call $ext_return
(i32.load8_u (i32.const 0)) ;; Exit status
(i32.const 4) ;; Pointer to the data to return.
(i32.sub ;; Count of bytes to copy.
(get_local $buf_size)
(i32.load (i32.const 128))
(i32.const 4)
)
)
;; Return the first 4 bytes of the input data as the exit status.
(i32.load (i32.const 0))
(unreachable)
)
)
@@ -1,11 +1,25 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_address" (func $ext_address))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_input" (func $ext_input (param i32 i32)))
(import "env" "ext_address" (func $ext_address (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_terminate" (func $ext_terminate (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) reserved for $ext_address output
;; [8, 16) length of the buffer
(data (i32.const 8) "\08")
;; [16, 24) Address of django
(data (i32.const 16) "\04\00\00\00\00\00\00\00")
;; [24, 32) reserved for output of $ext_input
;; [32, 36) length of the buffer
(data (i32.const 32) "\04")
;; [36, inf) zero initialized
(func $assert (param i32)
(block $ok
(br_if $ok
@@ -22,36 +36,32 @@
;; This should trap instead of self-destructing since a contract cannot be removed live in
;; the execution stack cannot be removed. If the recursive call traps, then trap here as
;; well.
(if (call $ext_scratch_size)
(call $ext_input (i32.const 24) (i32.const 32))
(if (i32.load (i32.const 32))
(then
(call $ext_address)
(call $ext_address (i32.const 0) (i32.const 8))
;; Expect address to be 8 bytes.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 8))
(i32.const 8)
)
)
;; Read own address into memory.
(call $ext_scratch_read
(i32.const 16) ;; Pointer to write address to
(i32.const 0) ;; Offset into scratch buffer
(i32.const 8) ;; Length of encoded address
)
;; Recursively call self with empty input data.
(call $assert
(i32.eq
(call $ext_call
(i32.const 16) ;; Pointer to own address
(i32.const 0) ;; Pointer to own address
(i32.const 8) ;; Length of own 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 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
(i32.const 0)
)
@@ -60,13 +70,11 @@
(else
;; Try to terminate and give balance to django.
(call $ext_terminate
(i32.const 32) ;; Pointer to beneficiary address
(i32.const 16) ;; Pointer to beneficiary address
(i32.const 8) ;; Length of beneficiary address
)
(unreachable) ;; ext_terminate never returns
)
)
)
;; Address of django
(data (i32.const 32) "\04\00\00\00\00\00\00\00")
)
@@ -1,10 +1,15 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_balance" (func $ext_balance))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result 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" "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)
(block $ok
(br_if $ok
@@ -16,34 +21,29 @@
(func (export "deploy")
;; Send entire remaining balance to the 0 address.
(call $ext_balance)
(call $ext_balance (i32.const 0) (i32.const 8))
;; Balance should be encoded as a u64.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 8))
(i32.const 8)
)
)
;; Read balance into memory.
(call $ext_scratch_read
(i32.const 8) ;; Pointer to write balance to
(i32.const 0) ;; Offset into scratch buffer
(i32.const 8) ;; Length of encoded balance
)
;; Self-destruct by sending full balance to the 0 address.
(call $assert
(i32.eq
(call $ext_call
(i32.const 0) ;; Pointer to 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 8) ;; 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 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)
)
+12 -18
View File
@@ -1,10 +1,9 @@
(module
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32) (result i32)))
(import "env" "ext_transfer" (func $ext_transfer (param i32 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_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_input" (func $ext_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; insert a value of 4 bytes into storage
@@ -25,12 +24,7 @@
;; transfer 50 to CHARLIE
(func $call_2
(call $assert
(i32.eq
(call $ext_transfer (i32.const 68) (i32.const 8) (i32.const 76) (i32.const 8))
(i32.const 0)
)
)
(call $ext_transfer (i32.const 68) (i32.const 8) (i32.const 76) (i32.const 8))
)
;; do nothing
@@ -48,8 +42,10 @@
;; Dispatch the call according to input size
(func (export "call")
(local $input_size i32)
(i32.store (i32.const 64) (i32.const 64))
(call $ext_input (i32.const 1024) (i32.const 64))
(set_local $input_size
(call $ext_scratch_size)
(i32.load (i32.const 64))
)
(block $IF_ELSE
(block $IF_2
@@ -75,29 +71,27 @@
;; Set into storage a 4 bytes value
;; Set call set_rent_allowance with input
(func (export "deploy")
(local $input_size i32)
(set_local $input_size
(call $ext_scratch_size)
)
(call $ext_set_storage
(i32.const 0)
(i32.const 0)
(i32.const 4)
)
(call $ext_scratch_read
(call $ext_input
(i32.const 0)
(i32.const 0)
(get_local $input_size)
(i32.const 64)
)
(call $ext_set_rent_allowance
(i32.const 0)
(get_local $input_size)
(i32.load (i32.const 64))
)
)
;; Encoding of 10 in balance
(data (i32.const 0) "\28")
;; Size of the buffer at address 0
(data (i32.const 64) "\40")
;; encoding of Charlies's account id
(data (i32.const 68) "\03")
@@ -1,10 +1,22 @@
(module
(import "env" "ext_get_storage" (func $ext_get_storage (param 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_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_input" (func $ext_input (param i32 i32)))
(import "env" "memory" (memory 16 16))
;; [0, 32) storage key
(data (i32.const 0) "\01")
;; [32, 36) buffer where input is copied (expected size of storage item)
;; [36, 40) size of the input buffer
(data (i32.const 36) "\04")
;; [40, 44) size of buffer for ext_get_storage set to max
(data (i32.const 40) "\FF\FF\FF\FF")
;; [44, inf) ext_get_storage buffer
(func $assert (param i32)
(block $ok
(br_if $ok
@@ -15,21 +27,16 @@
)
(func (export "call")
;; assert $ext_scratch_size == 8
(call $ext_input (i32.const 32) (i32.const 36))
;; assert input size == 4
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 36))
(i32.const 4)
)
)
;; copy contents of the scratch buffer into the contract's memory.
(call $ext_scratch_read
(i32.const 32) ;; Pointer in memory to the place where to copy.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 4) ;; Count of bytes to copy.
)
;; place a garbage value in storage, the size of which is specified by the call input.
(call $ext_set_storage
(i32.const 0) ;; Pointer to storage key
@@ -41,6 +48,8 @@
(i32.eq
(call $ext_get_storage
(i32.const 0) ;; Pointer to storage key
(i32.const 44) ;; buffer where to copy result
(i32.const 40) ;; pointer to size of buffer
)
(i32.const 0)
)
@@ -48,7 +57,7 @@
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 40))
(i32.load (i32.const 32))
)
)
@@ -56,5 +65,4 @@
(func (export "deploy"))
(data (i32.const 0) "\01") ;; Storage key (32 B)
)
@@ -35,12 +35,15 @@ pub enum ContractExecResult {
///
/// There is a status code and, optionally, some data returned by the contract.
Success {
/// Status code returned by the contract.
status: u8,
/// Flags that the contract passed along on returning to alter its exit behaviour.
/// Described in `pallet_contracts::exec::ReturnFlags`.
flags: u32,
/// Output data returned by the contract.
///
/// Can be empty.
data: Vec<u8>,
/// How much gas was consumed by the call.
gas_consumed: u64,
},
/// The contract execution either trapped or returned an error.
Error,
+12 -5
View File
@@ -92,10 +92,12 @@ pub struct CallRequest<AccountId, Balance> {
pub enum RpcContractExecResult {
/// Successful execution
Success {
/// Status code
status: u8,
/// The return flags
flags: u32,
/// Output data
data: Bytes,
/// How much gas was consumed by the call.
gas_consumed: u64,
},
/// Error execution
Error(()),
@@ -104,9 +106,14 @@ pub enum RpcContractExecResult {
impl From<ContractExecResult> for RpcContractExecResult {
fn from(r: ContractExecResult) -> Self {
match r {
ContractExecResult::Success { status, data } => RpcContractExecResult::Success {
status,
ContractExecResult::Success {
flags,
data,
gas_consumed
} => RpcContractExecResult::Success {
flags,
data: data.into(),
gas_consumed,
},
ContractExecResult::Error => RpcContractExecResult::Error(()),
}
@@ -309,7 +316,7 @@ mod tests {
let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, expected);
}
test(r#"{"success":{"status":5,"data":"0x1234"}}"#);
test(r#"{"success":{"flags":5,"data":"0x1234","gas_consumed":5000}}"#);
test(r#"{"error":null}"#);
}
}
+65 -142
View File
@@ -19,11 +19,12 @@ use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
use crate::gas::{Gas, GasMeter, Token};
use crate::rent;
use crate::storage;
use bitflags::bitflags;
use sp_std::prelude::*;
use sp_runtime::traits::{Bounded, Zero, Convert};
use frame_support::{
storage::unhashed, dispatch::DispatchError,
dispatch::DispatchError,
traits::{ExistenceRequirement, Currency, Time, Randomness},
weights::Weight,
};
@@ -37,58 +38,31 @@ pub type StorageKey = [u8; 32];
/// A type that represents a topic of an event. At the moment a hash is used.
pub type TopicOf<T> = <T as frame_system::Trait>::Hash;
/// A status code return to the source of a contract call or instantiation indicating success or
/// failure. A code of 0 indicates success and that changes are applied. All other codes indicate
/// failure and that changes are reverted. The particular code in the case of failure is opaque and
/// may be interpreted by the calling contract.
pub type StatusCode = u8;
/// The status code indicating success.
pub const STATUS_SUCCESS: StatusCode = 0;
bitflags! {
/// Flags used by a contract to customize exit behaviour.
pub struct ReturnFlags: u32 {
/// If this bit is set all changes made by the contract exection are rolled back.
const REVERT = 0x0000_0001;
}
}
/// Output of a contract call or instantiation which ran to completion.
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub struct ExecReturnValue {
pub status: StatusCode,
/// Flags passed along by `ext_return`. Empty when `ext_return` was never called.
pub flags: ReturnFlags,
/// Buffer passed along by `ext_return`. Empty when `ext_return` was never called.
pub data: Vec<u8>,
}
impl ExecReturnValue {
/// Returns whether the call or instantiation exited with a successful status code.
/// We understand the absense of a revert flag as success.
pub fn is_success(&self) -> bool {
self.status == STATUS_SUCCESS
!self.flags.contains(ReturnFlags::REVERT)
}
}
/// An error indicating some failure to execute a contract call or instantiation. This can include
/// VM-specific errors during execution (eg. division by 0, OOB access, failure to satisfy some
/// precondition of a system call, etc.) or errors with the orchestration (eg. out-of-gas errors, a
/// non-existent destination contract, etc.).
#[cfg_attr(test, derive(sp_runtime::RuntimeDebug))]
pub struct ExecError {
pub reason: DispatchError,
/// This is an allocated buffer that may be reused. The buffer must be cleared explicitly
/// before reuse.
pub buffer: Vec<u8>,
}
pub type ExecResult = Result<ExecReturnValue, ExecError>;
/// Evaluate an expression of type Result<_, &'static str> and either resolve to the value if Ok or
/// wrap the error string into an ExecutionError with the provided buffer and return from the
/// enclosing function. This macro is used instead of .map_err(..)? in order to avoid taking
/// ownership of buffer unless there is an error.
#[macro_export]
macro_rules! try_or_exec_error {
($e:expr, $buffer:expr) => {
match $e {
Ok(val) => val,
Err(reason) => return Err(
$crate::exec::ExecError { reason: reason.into(), buffer: $buffer }
),
}
}
}
pub type ExecResult = Result<ExecReturnValue, DispatchError>;
/// An interface that provides access to the external environment in which the
/// smart-contract is executed.
@@ -118,7 +92,7 @@ pub trait Ext {
value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
input_data: Vec<u8>,
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), DispatchError>;
/// Transfer some amount of funds into the specified account.
fn transfer(
@@ -208,11 +182,6 @@ pub trait Ext {
/// Returns the maximum allowed size of a storage item.
fn max_value_size(&self) -> u32;
/// Returns the value of runtime under the given key.
///
/// Returns `None` if the value doesn't exist.
fn get_runtime_storage(&self, key: &[u8]) -> Option<Vec<u8>>;
/// Returns the price for the specified amount of weight.
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T>;
}
@@ -331,20 +300,14 @@ where
input_data: Vec<u8>,
) -> ExecResult {
if self.depth == self.config.max_depth as usize {
return Err(ExecError {
reason: "reached maximum depth, cannot make a call".into(),
buffer: input_data,
});
Err("reached maximum depth, cannot make a call")?
}
if gas_meter
.charge(self.config, ExecFeeToken::Call)
.is_out_of_gas()
{
return Err(ExecError {
reason: "not enough gas to pay base call fee".into(),
buffer: input_data,
});
Err("not enough gas to pay base call fee")?
}
// Assumption: `collect_rent` doesn't collide with overlay because
@@ -354,10 +317,7 @@ where
// Calls to dead contracts always fail.
if let Some(ContractInfo::Tombstone(_)) = contract_info {
return Err(ExecError {
reason: "contract has been evicted".into(),
buffer: input_data,
});
Err("contract has been evicted")?
};
let caller = self.self_account.clone();
@@ -365,27 +325,21 @@ where
self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
if value > BalanceOf::<T>::zero() {
try_or_exec_error!(
transfer(
gas_meter,
TransferCause::Call,
&caller,
&dest,
value,
nested,
),
input_data
);
transfer(
gas_meter,
TransferCause::Call,
&caller,
&dest,
value,
nested,
)?
}
// If code_hash is not none, then the destination account is a live contract, otherwise
// it is a regular account since tombstone accounts have already been rejected.
match storage::code_hash::<T>(&dest) {
Ok(dest_code_hash) => {
let executable = try_or_exec_error!(
nested.loader.load_main(&dest_code_hash),
input_data
);
let executable = nested.loader.load_main(&dest_code_hash)?;
let output = nested.vm
.execute(
&executable,
@@ -395,7 +349,7 @@ where
)?;
Ok(output)
}
Err(storage::ContractAbsentError) => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }),
Err(storage::ContractAbsentError) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
}
})
}
@@ -406,22 +360,16 @@ where
gas_meter: &mut GasMeter<T>,
code_hash: &CodeHash<T>,
input_data: Vec<u8>,
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
) -> Result<(T::AccountId, ExecReturnValue), DispatchError> {
if self.depth == self.config.max_depth as usize {
return Err(ExecError {
reason: "reached maximum depth, cannot instantiate".into(),
buffer: input_data,
});
Err("reached maximum depth, cannot instantiate")?
}
if gas_meter
.charge(self.config, ExecFeeToken::Instantiate)
.is_out_of_gas()
{
return Err(ExecError {
reason: "not enough gas to pay base instantiate fee".into(),
buffer: input_data,
});
Err("not enough gas to pay base instantiate fee")?
}
let caller = self.self_account.clone();
@@ -437,36 +385,27 @@ where
let dest_trie_id = <T as Trait>::TrieIdGenerator::trie_id(&dest);
let output = self.with_nested_context(dest.clone(), Some(dest_trie_id), |nested| {
try_or_exec_error!(
storage::place_contract::<T>(
&dest,
nested
.self_trie_id
.clone()
.expect("the nested context always has to have self_trie_id"),
code_hash.clone()
),
input_data
);
storage::place_contract::<T>(
&dest,
nested
.self_trie_id
.clone()
.expect("the nested context always has to have self_trie_id"),
code_hash.clone()
)?;
// Send funds unconditionally here. If the `endowment` is below existential_deposit
// then error will be returned here.
try_or_exec_error!(
transfer(
gas_meter,
TransferCause::Instantiate,
&caller,
&dest,
endowment,
nested,
),
input_data
);
transfer(
gas_meter,
TransferCause::Instantiate,
&caller,
&dest,
endowment,
nested,
)?;
let executable = try_or_exec_error!(
nested.loader.load_init(&code_hash),
input_data
);
let executable = nested.loader.load_init(&code_hash)?;
let output = nested.vm
.execute(
&executable,
@@ -477,10 +416,7 @@ where
// Error out if insufficient remaining balance.
if T::Currency::free_balance(&dest) < nested.config.existential_deposit {
return Err(ExecError {
reason: "insufficient remaining balance".into(),
buffer: output.data,
});
Err("insufficient remaining balance")?
}
// Deposit an instantiation event.
@@ -518,7 +454,7 @@ where
frame_support::storage::with_transaction(|| {
let output = func(&mut nested);
match output {
Ok(ref rv) if rv.is_success() => Commit(output),
Ok(ref rv) if !rv.flags.contains(ReturnFlags::REVERT) => Commit(output),
_ => Rollback(output),
}
})
@@ -681,7 +617,7 @@ where
endowment: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>,
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
) -> Result<(AccountIdOf<T>, ExecReturnValue), DispatchError> {
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data)
}
@@ -839,10 +775,6 @@ where
self.ctx.config.max_value_size
}
fn get_runtime_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
unhashed::get_raw(&key)
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
T::WeightPrice::convert(weight)
}
@@ -867,11 +799,11 @@ fn deposit_event<T: Trait>(
mod tests {
use super::{
BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader,
RawEvent, TransferFeeKind, TransferFeeToken, Vm,
RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags,
};
use crate::{
gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config,
exec::ExecReturnValue, CodeHash, Config,
gas::Gas,
storage,
};
@@ -980,7 +912,7 @@ mod tests {
}
fn exec_success() -> ExecResult {
Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() })
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
}
#[test]
@@ -1096,7 +1028,7 @@ mod tests {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let return_ch = loader.insert(
|_| Ok(ExecReturnValue { status: 1, data: Vec::new() })
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() })
);
ExtBuilder::default().build().execute_with(|| {
@@ -1228,10 +1160,7 @@ mod tests {
assert_matches!(
result,
Err(ExecError {
reason: DispatchError::Module { message: Some("InsufficientBalance"), .. },
buffer: _,
})
Err(DispatchError::Module { message: Some("InsufficientBalance"), .. })
);
assert_eq!(get_balance(&origin), 0);
assert_eq!(get_balance(&dest), 0);
@@ -1248,7 +1177,7 @@ mod tests {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let return_ch = loader.insert(
|_| Ok(ExecReturnValue { status: STATUS_SUCCESS, data: vec![1, 2, 3, 4] })
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] })
);
ExtBuilder::default().build().execute_with(|| {
@@ -1279,7 +1208,7 @@ mod tests {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let return_ch = loader.insert(
|_| Ok(ExecReturnValue { status: 1, data: vec![1, 2, 3, 4] })
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] })
);
ExtBuilder::default().build().execute_with(|| {
@@ -1370,10 +1299,7 @@ mod tests {
// Verify that we've got proper error and set `reached_bottom`.
assert_matches!(
r,
Err(ExecError {
reason: DispatchError::Other("reached maximum depth, cannot make a call"),
buffer: _,
})
Err(DispatchError::Other("reached maximum depth, cannot make a call"))
);
*reached_bottom = true;
} else {
@@ -1517,7 +1443,7 @@ mod tests {
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Ok(ExecReturnValue { status: STATUS_SUCCESS, data: vec![80, 65, 83, 83] })
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
@@ -1550,7 +1476,7 @@ mod tests {
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Ok(ExecReturnValue { status: 1, data: vec![70, 65, 73, 76] })
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
@@ -1627,7 +1553,7 @@ mod tests {
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Err(ExecError { reason: "It's a trap!".into(), buffer: Vec::new() })
|_| Err("It's a trap!".into())
);
let instantiator_ch = loader.insert({
let dummy_ch = dummy_ch.clone();
@@ -1640,7 +1566,7 @@ mod tests {
ctx.gas_meter,
vec![]
),
Err(ExecError { reason: DispatchError::Other("It's a trap!"), buffer: _ })
Err(DispatchError::Other("It's a trap!"))
);
exec_success()
@@ -1691,10 +1617,7 @@ mod tests {
&terminate_ch,
vec![],
),
Err(ExecError {
reason: DispatchError::Other("insufficient remaining balance"),
buffer
}) if buffer == Vec::<u8>::new()
Err(DispatchError::Other("insufficient remaining balance"))
);
assert_eq!(
+2 -2
View File
@@ -178,8 +178,8 @@ impl<T: Trait> GasMeter<T> {
}
}
/// Returns how much gas left from the initial budget.
fn gas_spent(&self) -> Gas {
/// Returns how much gas was used.
pub fn gas_spent(&self) -> Gas {
self.gas_limit - self.gas_left
}
+17 -12
View File
@@ -93,7 +93,7 @@ use crate::exec::ExecutionContext;
use crate::wasm::{WasmLoader, WasmVm};
pub use crate::gas::{Gas, GasMeter};
pub use crate::exec::{ExecResult, ExecReturnValue, ExecError, StatusCode};
pub use crate::exec::{ExecResult, ExecReturnValue};
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
@@ -410,7 +410,11 @@ decl_error! {
/// Tombstones don't match.
InvalidTombstone,
/// An origin TrieId written in the current block.
InvalidContractOrigin
InvalidContractOrigin,
/// The executed contract exhausted its gas limit.
OutOfGas,
/// The output buffer supplied to a contract API call was too small.
OutputBufferTooSmall,
}
}
@@ -515,7 +519,7 @@ decl_module! {
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, data)
});
gas_meter.into_dispatch_result(result.map_err(|e| e.reason))
gas_meter.into_dispatch_result(result)
}
/// Instantiates a new contract from the `codehash` generated by `put_code`, optionally transferring some balance.
@@ -543,7 +547,7 @@ decl_module! {
ctx.instantiate(endowment, gas_meter, &code_hash, data)
.map(|(_address, output)| output)
});
gas_meter.into_dispatch_result(result.map_err(|e| e.reason))
gas_meter.into_dispatch_result(result)
}
/// Allows block producers to claim a small reward for evicting a contract. If a block producer
@@ -587,17 +591,22 @@ impl<T: Trait> Module<T> {
///
/// This function is similar to `Self::call`, but doesn't perform any address lookups and better
/// suitable for calling directly from Rust.
///
/// It returns the exection result and the amount of used weight.
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
value: BalanceOf<T>,
gas_limit: Gas,
input_data: Vec<u8>,
) -> ExecResult {
) -> (ExecResult, Gas) {
let mut gas_meter = GasMeter::new(gas_limit);
Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, input_data)
})
(
Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, input_data)
}),
gas_meter.gas_spent(),
)
}
/// Query storage of a specified contract under a specified key.
@@ -673,10 +682,6 @@ decl_event! {
/// Triggered when the current schedule is updated.
ScheduleUpdated(u32),
/// A call was dispatched from the given account. The bool signals whether it was
/// successful execution or not.
Dispatched(AccountId, bool),
/// An event deposited upon execution of a contract from the account.
ContractExecution(AccountId, Vec<u8>),
}
+6 -5
View File
@@ -17,6 +17,7 @@
use crate::{
BalanceOf, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, Module,
RawAliveContractInfo, RawEvent, Trait, TrieId, Schedule, TrieIdGenerator, gas::Gas,
Error,
};
use assert_matches::assert_matches;
use hex_literal::*;
@@ -478,7 +479,7 @@ fn run_out_of_gas() {
67_500_000,
vec![],
),
"ran out of gas during contract execution"
Error::<Test>::OutOfGas,
);
});
}
@@ -1169,7 +1170,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
DJANGO,
0,
GAS_LIMIT,
vec![],
set_rent_code_hash.as_ref().to_vec(),
)
};
@@ -1294,7 +1295,7 @@ fn storage_max_value_limit() {
Origin::signed(ALICE),
BOB,
0,
GAS_LIMIT,
GAS_LIMIT * 2, // we are copying a huge buffer
Encode::encode(&self::MaxValueSize::get()),
));
@@ -1594,8 +1595,8 @@ fn crypto_hashes() {
0,
GAS_LIMIT,
params,
).unwrap();
assert_eq!(result.status, 0);
).0.unwrap();
assert!(result.is_success());
let expected = hash_fn(input.as_ref());
assert_eq!(&result.data[..*expected_size], &*expected);
}
File diff suppressed because it is too large Load Diff
@@ -227,11 +227,7 @@ impl<'a> ContractModule<'a> {
};
// Then check the signature.
// Both "call" and "deploy" has a [] -> [] or [] -> [i32] function type.
//
// The [] -> [] signature predates the [] -> [i32] signature and is supported for
// backwards compatibility. This will likely be removed once ink! is updated to
// generate modules with the new function signatures.
// Both "call" and "deploy" has a () -> () function type.
let func_ty_idx = func_entries.get(fn_idx as usize)
.ok_or_else(|| "export refers to non-existent function")?
.type_ref();
File diff suppressed because it is too large Load Diff