mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
Rename: srml-contract → srml-contracts (#2905)
* srml-contract → srml-contracts * Trim. * Bump version
This commit is contained in:
committed by
Bastian Köcher
parent
37acb90847
commit
828485ec08
@@ -0,0 +1,398 @@
|
||||
# Complexity
|
||||
|
||||
This analysis is on the computing and memory complexity of specific procedures. It provides a rough estimate of operations performed in general and especially focusing on DB reads and writes. It is also an attempt to estimate the memory consumption at its peak.
|
||||
|
||||
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
|
||||
|
||||
It makes sense to describe the sandboxing module first because the smart-contract module is built upon it.
|
||||
|
||||
## Memory
|
||||
|
||||
### 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
|
||||
|
||||
Copies data from the guest's memory to the supervisor's memory.
|
||||
|
||||
**complexity**: It doesn't allocate, and the computational complexity is proportional to the number of bytes to copy.
|
||||
|
||||
## Instance
|
||||
|
||||
### Instantiation
|
||||
|
||||
Instantiation of a sandbox module consists of the following steps:
|
||||
|
||||
1. Loading the wasm module in the in-memory representation,
|
||||
2. Performing validation of the wasm code,
|
||||
3. Setting up the environment which will be used to instantiate the module,
|
||||
4. Performing the standard wasm instantiation process, which includes (but is not limited to):
|
||||
1. Allocating of memory requested by the instance,
|
||||
2. Copying static data from the module to newly allocated memory,
|
||||
3. Executing the `start` function.
|
||||
|
||||
**Note** that the `start` function can be viewed as a normal function and can do anything that a normal function can do, including allocation of more memory or calling the host environment. The complexity of running the `start` function should be considered separately.
|
||||
|
||||
In order to start the process of instantiation, the supervisor should provide the wasm module code being instantiated and the environment definition (a set of functions, memories (and maybe globals and tables in the future) available for import by the guest module) for that module. While the environment definition typically is of the constant size (unless mechanisms like dynamic linking are used), the size of wasm is not.
|
||||
|
||||
Validation and instantiation in WebAssembly are designed to be able to be performed in linear time. The allocation and computational complexity of loading a wasm module depend on the underlying wasm VM being used. For example, for JIT compilers it can and probably will be non-linear because of compilation. However, for wasmi, it should be linear. We can try to use other VMs that are able to compile code with memory and time consumption proportional to the size of the code.
|
||||
|
||||
Since the module itself requests memory, the amount of allocation depends on the module code itself. If untrusted code is being instantiated, it's up to the supervisor to limit the amount of memory available to allocate.
|
||||
|
||||
**complexity**: The computational complexity is proportional to the size of wasm code. Memory complexity is proportional to the size of wasm code and the amount of memory requested by the module.
|
||||
|
||||
### Preparation to invoke
|
||||
|
||||
Invocation of an exported function in the sandboxed module consists of the following steps:
|
||||
|
||||
1. Marshalling, copying and unmarshalling the arguments when passing them between the supervisor and executor,
|
||||
2. Calling into the underlying VM,
|
||||
3. Marshalling, copying and unmarshalling the result when passing it between the executor and supervisor,
|
||||
|
||||
**Note** that the complexity of running the function code itself should be considered separately.
|
||||
|
||||
The actual complexity of invocation depends on the underlying VM. Wasmi will reserve a relatively large chunk of memory for the stack before execution of the code, although it's of constant size.
|
||||
|
||||
The size of the arguments and the return value depends on the exact function in question, but can be considered as constant.
|
||||
|
||||
**complexity**: Memory and computational complexity can be considered as a constant.
|
||||
|
||||
### Call from the guest to the supervisor
|
||||
|
||||
The executor handles each call from the guest. The execution of it consists of the following steps:
|
||||
|
||||
1. Marshalling, copying and unmarshalling the arguments when passing them between the guest and executor,
|
||||
2. Calling into the supervisor,
|
||||
3. Marshaling, copying and unmarshalling the result when passing it between the executor and guest.
|
||||
|
||||
**Note** that the complexity of running the supervisor handler should be considered separately.
|
||||
|
||||
Because calling into the supervisor requires invoking a wasm VM, the actual complexity of invocation depends on the actual VM used for the runtime/supervisor. Wasmi will reserve a relatively large chunk of memory for the stack before execution of the code, although it's of constant size.
|
||||
|
||||
The size of the arguments and the return value depends on the exact function in question, but can be considered as a constant.
|
||||
|
||||
**complexity**: Memory and computational complexity can be considered as a constant.
|
||||
|
||||
# `AccountDb`
|
||||
|
||||
`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 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
|
||||
> placed into the overlay. The overlay is only filled with writes.
|
||||
>
|
||||
> This means that the overlay can be abused in the following ways:
|
||||
>
|
||||
> - The overlay can be inflated by issuing a lot of writes to unique locations,
|
||||
> - Deliberate cache misses can be induced by reading non-modified storage locations,
|
||||
|
||||
It also worth noting that the performance degrades with more state stored in the trie. Due to this
|
||||
there is not negligible chance that gas schedule will be updated for all operations that involve
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
## set_storage, set_balance, set_rent_allowance
|
||||
|
||||
These functions only modify the local `Map`.
|
||||
|
||||
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 `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.
|
||||
|
||||
## create_contract
|
||||
|
||||
Calls `contract_exists` and if it doesn't exist, do not modify the local `Map` similarly to `set_rent_allowance`.
|
||||
|
||||
**complexity**: 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. No additional memory is required.
|
||||
|
||||
## commit
|
||||
|
||||
In this function, all cached values will be inserted into the underlying `AccountDb` or into the storage.
|
||||
|
||||
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.
|
||||
|
||||
Note that in case of storage modification we need to construct a key in the underlying storage. In order to do that we need:
|
||||
|
||||
- perform `twox_128` hashing over a concatenation of some prefix literal and the `AccountId` of the storage owner.
|
||||
- then perform `blake2_256` hashing of the storage key.
|
||||
- concatenation of these hashes will constitute the key in the underlying storage.
|
||||
|
||||
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 proprotional 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.
|
||||
|
||||
## revert
|
||||
|
||||
Consists of dropping (in the Rust sense) of the `AccountDb`.
|
||||
|
||||
**complexity**: Computing complexity is proportional to a number of changed entries in a overlay. No additional memory is required.
|
||||
|
||||
# Executive
|
||||
|
||||
## Transfer
|
||||
|
||||
This function performs the following steps:
|
||||
|
||||
1. Querying source and destination balances from an overlay (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`).
|
||||
|
||||
**Note** that the complexity of executing `ensure_account_liquid` hook should be considered separately.
|
||||
|
||||
In the course of the execution this function can perform up to 2 DB reads to `get_balance` of source and destination accounts. It can also induce up to 2 DB writes via `set_balance` if flushed to the storage.
|
||||
|
||||
Moreover, if the source balance goes below `existential_deposit` then the account will be deleted along with all its storage which requires time proportional to the number of storage entries of that account.
|
||||
|
||||
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.
|
||||
|
||||
## Call
|
||||
|
||||
This function receives input data for the contract execution. The execution consists of the following steps:
|
||||
|
||||
1. Checking rent payment.
|
||||
2. Loading code from the DB.
|
||||
3. `transfer`-ing funds between the caller and the destination account.
|
||||
4. Executing the code of the destination account.
|
||||
5. Committing overlayed changed to the underlying `AccountDb`.
|
||||
|
||||
**Note** that the complexity of executing the contract code should be considered separately.
|
||||
|
||||
Checking for rent involves 2 unconditional DB reads: `ContractInfoOf` and `block_number`
|
||||
and on top of that at most once per block:
|
||||
|
||||
- DB read to `free_balance` and
|
||||
- `rent_deposit_offset` and
|
||||
- `rent_byte_price` and
|
||||
- `Currency::minimum_balance` and
|
||||
- `tombstone_deposit`.
|
||||
- Calls to `ensure_can_withdraw`, `withdraw`, `make_free_balance_be` can perform arbitrary logic and should be considered separately,
|
||||
- `child_storage_root`
|
||||
- `kill_child_storage`
|
||||
- mutation of `ContractInfoOf`
|
||||
|
||||
Loading code most likely will trigger a DB read, since the code is immutable and therefore will not get into the cache (unless a suicide removes it, or it has been created in the same call chain).
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
## Create
|
||||
|
||||
This function takes the code of the constructor and input data. Creation of a contract consists of the following steps:
|
||||
|
||||
1. Calling `DetermineContractAddress` hook to determine an address for the contract,
|
||||
2. `transfer`-ing funds between self and the newly created contract.
|
||||
3. Executing the constructor code. This will yield the final code of the code.
|
||||
4. Storing the code for the newly created contract in the overlay.
|
||||
5. Committing overlayed changed to the underlying `AccountDb`.
|
||||
|
||||
**Note** that the complexity of executing the constructor code should be considered separately.
|
||||
|
||||
**Note** that the complexity of `DetermineContractAddress` hook should be considered separately as well. Most likely it will use some kind of hashing over the code of the constructor and input data. The default `SimpleAddressDeterminator` does precisely that.
|
||||
|
||||
**Note** that the constructor returns code in the owned form and it's obtained via return facilities, which should have take fee for the return value.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
**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
|
||||
|
||||
Each external function invoked from a contract can involve some overhead.
|
||||
|
||||
## ext_gas
|
||||
|
||||
**complexity**: This is of constant complexity.
|
||||
|
||||
## ext_set_storage
|
||||
|
||||
This function receives a `key` and `value` as arguments. It consists of the following steps:
|
||||
|
||||
1. Reading the sandbox memory for `key` and `value` (see sandboxing memory get).
|
||||
2. Setting the storage by the given `key` with the given `value` (see `set_storage`).
|
||||
|
||||
**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_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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
It consists of the following steps:
|
||||
|
||||
1. Loading `callee` 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.
|
||||
3. Loading `input_data` buffer from the sandbox memory.
|
||||
4. Invoking the executive function `call`.
|
||||
|
||||
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.
|
||||
|
||||
## ext_create
|
||||
|
||||
This function receives the following arguments:
|
||||
|
||||
- `init_code`, a buffer which contains the code of the constructor.
|
||||
- `gas` limit which is plain u64
|
||||
- `value` buffer of a marshaled `Balance`
|
||||
- `input_data`. an arbitrarily sized byte vector.
|
||||
|
||||
It consists of the following steps:
|
||||
|
||||
1. Loading `init_code` 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.
|
||||
3. Loading `input_data` buffer from the sandbox memory.
|
||||
4. Invoking `create` executive function.
|
||||
|
||||
Loading of `value` buffer should be charged. This is because the size of the buffer is specified by the calling code, even though marshaled representation is, essentially, of constant size. This can be fixed by assigning an upper bound for size for `Balance`.
|
||||
|
||||
Loading `init_code` and `input_data` should be charged in any case.
|
||||
|
||||
**complexity**: All complexity comes from loading buffers and executing `create` 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 `create` executive function and also dominated by it.
|
||||
|
||||
## ext_return
|
||||
|
||||
This function receives a `data` buffer as an argument. Execution of the function consists of the following steps:
|
||||
|
||||
1. Loading `data` buffer from the sandbox memory (see sandboxing memory get),
|
||||
2. Trapping
|
||||
|
||||
**complexity**: The complexity of this function is proportional to the size of the `data` buffer.
|
||||
|
||||
## ext_deposit_event
|
||||
|
||||
This function receives a `data` buffer as an argument. Execution of the function consists of the following steps:
|
||||
|
||||
1. Loading `data` buffer from the sandbox memory (see sandboxing memory get),
|
||||
2. Insert to nested context execution
|
||||
3. Copies from nested to underlying contexts
|
||||
4. Call system deposit event
|
||||
|
||||
**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_input_size
|
||||
|
||||
**complexity**: This function is of constant complexity.
|
||||
|
||||
## ext_input_copy
|
||||
|
||||
This function copies slice of data from the input 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 input data 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_size
|
||||
|
||||
This function returns the size of the scratch buffer.
|
||||
|
||||
**complexity**: This function is of constant complexity.
|
||||
|
||||
## ext_scratch_copy
|
||||
|
||||
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_set_rent_allowance
|
||||
|
||||
This function receives the following argument:
|
||||
|
||||
- `value` buffer of a marshaled `Balance`,
|
||||
|
||||
It consists of the following steps:
|
||||
|
||||
1. Loading `value` buffer from the sandbox memory and then decoding it.
|
||||
2. Invoking `set_rent_allowance` AccountDB function.
|
||||
|
||||
**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.
|
||||
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "srml-contracts"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
pwasm-utils = { version = "0.6.1", default-features = false }
|
||||
parity-codec = { version = "3.3", default-features = false, features = ["derive"] }
|
||||
parity-wasm = { version = "0.31", default-features = false }
|
||||
wasmi-validation = { version = "0.1", default-features = false }
|
||||
substrate-primitives = { path = "../../core/primitives", default-features = false }
|
||||
runtime-primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
runtime-io = { package = "sr-io", path = "../../core/sr-io", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
sandbox = { package = "sr-sandbox", path = "../../core/sr-sandbox", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
wabt = "~0.7.4"
|
||||
assert_matches = "1.1"
|
||||
hex-literal = "0.2.0"
|
||||
balances = { package = "srml-balances", path = "../balances" }
|
||||
hex = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
core = [
|
||||
"wasmi-validation/core",
|
||||
]
|
||||
std = [
|
||||
"serde",
|
||||
"parity-codec/std",
|
||||
"substrate-primitives/std",
|
||||
"runtime-primitives/std",
|
||||
"runtime-io/std",
|
||||
"rstd/std",
|
||||
"sandbox/std",
|
||||
"srml-support/std",
|
||||
"system/std",
|
||||
"timestamp/std",
|
||||
"parity-wasm/std",
|
||||
"pwasm-utils/std",
|
||||
"wasmi-validation/std",
|
||||
]
|
||||
@@ -0,0 +1,307 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Auxilliaries to help with managing partial changes to accounts state.
|
||||
|
||||
use super::{
|
||||
AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Module, Trait, TrieId,
|
||||
TrieIdGenerator,
|
||||
};
|
||||
use crate::exec::StorageKey;
|
||||
use rstd::cell::RefCell;
|
||||
use rstd::collections::btree_map::{BTreeMap, Entry};
|
||||
use rstd::prelude::*;
|
||||
use runtime_io::blake2_256;
|
||||
use runtime_primitives::traits::{Bounded, Zero};
|
||||
use srml_support::traits::{Currency, Imbalance, SignedImbalance, UpdateBalanceOutcome};
|
||||
use srml_support::{storage::child, StorageMap};
|
||||
use system;
|
||||
|
||||
// Note: we don't provide Option<Contract> because we can't create
|
||||
// the trie_id in the overlay, thus we provide an overlay on the fields
|
||||
// specifically.
|
||||
pub struct ChangeEntry<T: Trait> {
|
||||
balance: Option<BalanceOf<T>>,
|
||||
/// If None, the code_hash remains untouched.
|
||||
code_hash: Option<CodeHash<T>>,
|
||||
rent_allowance: Option<BalanceOf<T>>,
|
||||
storage: BTreeMap<StorageKey, Option<Vec<u8>>>,
|
||||
}
|
||||
|
||||
// Cannot derive(Default) since it erroneously bounds T by Default.
|
||||
impl<T: Trait> Default for ChangeEntry<T> {
|
||||
fn default() -> Self {
|
||||
ChangeEntry {
|
||||
rent_allowance: Default::default(),
|
||||
balance: Default::default(),
|
||||
code_hash: Default::default(),
|
||||
storage: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ChangeSet<T> = BTreeMap<<T as system::Trait>::AccountId, ChangeEntry<T>>;
|
||||
|
||||
pub trait AccountDb<T: Trait> {
|
||||
/// Account is used when overlayed otherwise trie_id must be provided.
|
||||
/// This is for performance reason.
|
||||
///
|
||||
/// Trie id is None iff account doesn't have an associated trie id in <ContractInfoOf<T>>.
|
||||
/// Because DirectAccountDb bypass the lookup for this association.
|
||||
fn get_storage(&self, account: &T::AccountId, trie_id: Option<&TrieId>, location: &StorageKey) -> Option<Vec<u8>>;
|
||||
/// If account has an alive contract then return the code hash associated.
|
||||
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>>;
|
||||
/// If account has an alive contract then return the rent allowance associated.
|
||||
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>>;
|
||||
/// Returns false iff account has no alive contract nor tombstone.
|
||||
fn contract_exists(&self, account: &T::AccountId) -> bool;
|
||||
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T>;
|
||||
|
||||
fn commit(&mut self, change_set: ChangeSet<T>);
|
||||
}
|
||||
|
||||
pub struct DirectAccountDb;
|
||||
impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
||||
fn get_storage(
|
||||
&self,
|
||||
_account: &T::AccountId,
|
||||
trie_id: Option<&TrieId>,
|
||||
location: &StorageKey
|
||||
) -> Option<Vec<u8>> {
|
||||
trie_id.and_then(|id| child::get_raw(id, &blake2_256(location)))
|
||||
}
|
||||
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
||||
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash))
|
||||
}
|
||||
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
|
||||
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance))
|
||||
}
|
||||
fn contract_exists(&self, account: &T::AccountId) -> bool {
|
||||
<ContractInfoOf<T>>::exists(account)
|
||||
}
|
||||
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
|
||||
T::Currency::free_balance(account)
|
||||
}
|
||||
fn commit(&mut self, s: ChangeSet<T>) {
|
||||
let mut total_imbalance = SignedImbalance::zero();
|
||||
for (address, changed) in s.into_iter() {
|
||||
if let Some(balance) = changed.balance {
|
||||
let (imbalance, outcome) = T::Currency::make_free_balance_be(&address, balance);
|
||||
total_imbalance = total_imbalance.merge(imbalance);
|
||||
if let UpdateBalanceOutcome::AccountKilled = outcome {
|
||||
// Account killed. This will ultimately lead to calling `OnFreeBalanceZero` callback
|
||||
// which will make removal of CodeHashOf and AccountStorage for this account.
|
||||
// In order to avoid writing over the deleted properties we `continue` here.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if changed.code_hash.is_some()
|
||||
|| changed.rent_allowance.is_some()
|
||||
|| !changed.storage.is_empty()
|
||||
{
|
||||
let old_info = match <ContractInfoOf<T>>::get(&address) {
|
||||
Some(ContractInfo::Alive(alive)) => Some(alive),
|
||||
None => None,
|
||||
// Cannot commit changes to tombstone contract
|
||||
Some(ContractInfo::Tombstone(_)) => continue,
|
||||
};
|
||||
|
||||
let mut new_info = if let Some(info) = old_info.clone() {
|
||||
info
|
||||
} else if let Some(code_hash) = changed.code_hash {
|
||||
AliveContractInfo::<T> {
|
||||
code_hash,
|
||||
storage_size: <Module<T>>::storage_size_offset(),
|
||||
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
|
||||
deduct_block: <system::Module<T>>::block_number(),
|
||||
rent_allowance: <BalanceOf<T>>::max_value(),
|
||||
last_write: None,
|
||||
}
|
||||
} else {
|
||||
// No contract exist and no code_hash provided
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(rent_allowance) = changed.rent_allowance {
|
||||
new_info.rent_allowance = rent_allowance;
|
||||
}
|
||||
|
||||
if let Some(code_hash) = changed.code_hash {
|
||||
new_info.code_hash = code_hash;
|
||||
}
|
||||
|
||||
if !changed.storage.is_empty() {
|
||||
new_info.last_write = Some(<system::Module<T>>::block_number());
|
||||
}
|
||||
|
||||
for (k, v) in changed.storage.into_iter() {
|
||||
if let Some(value) = child::get_raw(&new_info.trie_id[..], &blake2_256(&k)) {
|
||||
new_info.storage_size -= value.len() as u32;
|
||||
}
|
||||
if let Some(value) = v {
|
||||
new_info.storage_size += value.len() as u32;
|
||||
child::put_raw(&new_info.trie_id[..], &blake2_256(&k), &value[..]);
|
||||
} else {
|
||||
child::kill(&new_info.trie_id[..], &blake2_256(&k));
|
||||
}
|
||||
}
|
||||
|
||||
if old_info
|
||||
.map(|old_info| old_info != new_info)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
<ContractInfoOf<T>>::insert(&address, ContractInfo::Alive(new_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match total_imbalance {
|
||||
// If we've detected a positive imbalance as a result of our contract-level machinations
|
||||
// then it's indicative of a buggy contracts system.
|
||||
// Panicking is far from ideal as it opens up a DoS attack on block validators, however
|
||||
// it's a less bad option than allowing arbitrary value to be created.
|
||||
SignedImbalance::Positive(ref p) if !p.peek().is_zero() =>
|
||||
panic!("contract subsystem resulting in positive imbalance!"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct OverlayAccountDb<'a, T: Trait + 'a> {
|
||||
local: RefCell<ChangeSet<T>>,
|
||||
underlying: &'a dyn AccountDb<T>,
|
||||
}
|
||||
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
||||
pub fn new(underlying: &'a dyn AccountDb<T>) -> OverlayAccountDb<'a, T> {
|
||||
OverlayAccountDb {
|
||||
local: RefCell::new(ChangeSet::new()),
|
||||
underlying,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_change_set(self) -> ChangeSet<T> {
|
||||
self.local.into_inner()
|
||||
}
|
||||
|
||||
pub fn set_storage(
|
||||
&mut self,
|
||||
account: &T::AccountId,
|
||||
location: StorageKey,
|
||||
value: Option<Vec<u8>>,
|
||||
) {
|
||||
self.local.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.storage
|
||||
.insert(location, value);
|
||||
}
|
||||
|
||||
/// Return an error if contract already exists (either if it is alive or tombstone)
|
||||
pub fn create_contract(
|
||||
&mut self,
|
||||
account: &T::AccountId,
|
||||
code_hash: CodeHash<T>,
|
||||
) -> Result<(), &'static str> {
|
||||
if self.contract_exists(account) {
|
||||
return Err("Alive contract or tombstone already exists");
|
||||
}
|
||||
|
||||
let mut local = self.local.borrow_mut();
|
||||
let contract = local.entry(account.clone()).or_insert_with(|| Default::default());
|
||||
|
||||
contract.code_hash = Some(code_hash);
|
||||
contract.rent_allowance = Some(<BalanceOf<T>>::max_value());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Assume contract exists
|
||||
pub fn set_rent_allowance(&mut self, account: &T::AccountId, rent_allowance: BalanceOf<T>) {
|
||||
self.local
|
||||
.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.rent_allowance = Some(rent_allowance);
|
||||
}
|
||||
pub fn set_balance(&mut self, account: &T::AccountId, balance: BalanceOf<T>) {
|
||||
self.local
|
||||
.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.balance = Some(balance);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
|
||||
fn get_storage(
|
||||
&self,
|
||||
account: &T::AccountId,
|
||||
trie_id: Option<&TrieId>,
|
||||
location: &StorageKey
|
||||
) -> Option<Vec<u8>> {
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|a| a.storage.get(location))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.underlying.get_storage(account, trie_id, location))
|
||||
}
|
||||
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|changes| changes.code_hash)
|
||||
.or_else(|| self.underlying.get_code_hash(account))
|
||||
}
|
||||
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|changes| changes.rent_allowance)
|
||||
.or_else(|| self.underlying.get_rent_allowance(account))
|
||||
}
|
||||
fn contract_exists(&self, account: &T::AccountId) -> bool {
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.map(|a| a.code_hash.is_some())
|
||||
.unwrap_or_else(|| self.underlying.contract_exists(account))
|
||||
}
|
||||
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|a| a.balance)
|
||||
.unwrap_or_else(|| self.underlying.get_balance(account))
|
||||
}
|
||||
fn commit(&mut self, s: ChangeSet<T>) {
|
||||
let mut local = self.local.borrow_mut();
|
||||
|
||||
for (address, changed) in s.into_iter() {
|
||||
match local.entry(address) {
|
||||
Entry::Occupied(e) => {
|
||||
let mut value = e.into_mut();
|
||||
value.balance = changed.balance.or(value.balance);
|
||||
value.code_hash = changed.code_hash.or(value.code_hash);
|
||||
value.rent_allowance = changed.rent_allowance.or(value.rent_allowance);
|
||||
value.storage.extend(changed.storage.into_iter());
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,360 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{GasSpent, Module, Trait, BalanceOf, NegativeImbalanceOf};
|
||||
use runtime_primitives::BLOCK_FULL;
|
||||
use runtime_primitives::traits::{CheckedMul, CheckedSub, Zero, SaturatedConversion};
|
||||
use srml_support::{StorageValue, traits::{OnUnbalanced, ExistenceRequirement, WithdrawReason, Currency, Imbalance}};
|
||||
|
||||
#[cfg(test)]
|
||||
use std::{any::Any, fmt::Debug};
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum GasMeterResult {
|
||||
Proceed,
|
||||
OutOfGas,
|
||||
}
|
||||
|
||||
impl GasMeterResult {
|
||||
pub fn is_out_of_gas(&self) -> bool {
|
||||
match *self {
|
||||
GasMeterResult::OutOfGas => true,
|
||||
GasMeterResult::Proceed => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub trait TestAuxiliaries {}
|
||||
#[cfg(not(test))]
|
||||
impl<T> TestAuxiliaries for T {}
|
||||
|
||||
#[cfg(test)]
|
||||
pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
|
||||
#[cfg(test)]
|
||||
impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
|
||||
|
||||
/// This trait represents a token that can be used for charging `GasMeter`.
|
||||
/// There is no other way of charging it.
|
||||
///
|
||||
/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
|
||||
/// for consistency). If inlined there should be no observable difference compared
|
||||
/// to a hand-written code.
|
||||
pub trait Token<T: Trait>: Copy + Clone + TestAuxiliaries {
|
||||
/// Metadata type, which the token can require for calculating the amount
|
||||
/// of gas to charge. Can be a some configuration type or
|
||||
/// just the `()`.
|
||||
type Metadata;
|
||||
|
||||
/// Calculate amount of gas that should be taken by this token.
|
||||
///
|
||||
/// This function should be really lightweight and must not fail. It is not
|
||||
/// expected that implementors will query the storage or do any kinds of heavy operations.
|
||||
///
|
||||
/// That said, implementors of this function still can run into overflows
|
||||
/// while calculating the amount. In this case it is ok to use saturating operations
|
||||
/// since on overflow they will return `max_value` which should consume all gas.
|
||||
fn calculate_amount(&self, metadata: &Self::Metadata) -> T::Gas;
|
||||
}
|
||||
|
||||
/// A wrapper around a type-erased trait object of what used to be a `Token`.
|
||||
#[cfg(test)]
|
||||
pub struct ErasedToken {
|
||||
pub description: String,
|
||||
pub token: Box<dyn Any>,
|
||||
}
|
||||
|
||||
pub struct GasMeter<T: Trait> {
|
||||
limit: T::Gas,
|
||||
/// Amount of gas left from initial gas limit. Can reach zero.
|
||||
gas_left: T::Gas,
|
||||
gas_price: BalanceOf<T>,
|
||||
|
||||
#[cfg(test)]
|
||||
tokens: Vec<ErasedToken>,
|
||||
}
|
||||
impl<T: Trait> GasMeter<T> {
|
||||
#[cfg(test)]
|
||||
pub fn with_limit(gas_limit: T::Gas, gas_price: BalanceOf<T>) -> GasMeter<T> {
|
||||
GasMeter {
|
||||
limit: gas_limit,
|
||||
gas_left: gas_limit,
|
||||
gas_price,
|
||||
#[cfg(test)]
|
||||
tokens: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Account for used gas.
|
||||
///
|
||||
/// Amount is calculated by the given `token`.
|
||||
///
|
||||
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
|
||||
/// amount of gas has lead to overflow. On success returns `Proceed`.
|
||||
///
|
||||
/// NOTE that amount is always consumed, i.e. if there is not enough gas
|
||||
/// then the counter will be set to zero.
|
||||
#[inline]
|
||||
pub fn charge<Tok: Token<T>>(
|
||||
&mut self,
|
||||
metadata: &Tok::Metadata,
|
||||
token: Tok,
|
||||
) -> GasMeterResult {
|
||||
#[cfg(test)]
|
||||
{
|
||||
// Unconditionally add the token to the storage.
|
||||
let erased_tok = ErasedToken {
|
||||
description: format!("{:?}", token),
|
||||
token: Box::new(token),
|
||||
};
|
||||
self.tokens.push(erased_tok);
|
||||
}
|
||||
|
||||
let amount = token.calculate_amount(metadata);
|
||||
let new_value = match self.gas_left.checked_sub(&amount) {
|
||||
None => None,
|
||||
Some(val) if val.is_zero() => None,
|
||||
Some(val) => Some(val),
|
||||
};
|
||||
|
||||
// We always consume the gas even if there is not enough gas.
|
||||
self.gas_left = new_value.unwrap_or_else(Zero::zero);
|
||||
|
||||
match new_value {
|
||||
Some(_) => GasMeterResult::Proceed,
|
||||
None => GasMeterResult::OutOfGas,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate some amount of gas and perform some work with
|
||||
/// a newly created nested gas meter.
|
||||
///
|
||||
/// Invokes `f` with either the gas meter that has `amount` gas left or
|
||||
/// with `None`, if this gas meter has not enough gas to allocate given `amount`.
|
||||
///
|
||||
/// All unused gas in the nested gas meter is returned to this gas meter.
|
||||
pub fn with_nested<R, F: FnOnce(Option<&mut GasMeter<T>>) -> R>(
|
||||
&mut self,
|
||||
amount: T::Gas,
|
||||
f: F,
|
||||
) -> R {
|
||||
// NOTE that it is ok to allocate all available gas since it still ensured
|
||||
// by `charge` that it doesn't reach zero.
|
||||
if self.gas_left < amount {
|
||||
f(None)
|
||||
} else {
|
||||
self.gas_left = self.gas_left - amount;
|
||||
let mut nested = GasMeter {
|
||||
limit: amount,
|
||||
gas_left: amount,
|
||||
gas_price: self.gas_price,
|
||||
#[cfg(test)]
|
||||
tokens: Vec::new(),
|
||||
};
|
||||
|
||||
let r = f(Some(&mut nested));
|
||||
|
||||
self.gas_left = self.gas_left + nested.gas_left;
|
||||
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gas_price(&self) -> BalanceOf<T> {
|
||||
self.gas_price
|
||||
}
|
||||
|
||||
/// Returns how much gas left from the initial budget.
|
||||
pub fn gas_left(&self) -> T::Gas {
|
||||
self.gas_left
|
||||
}
|
||||
|
||||
/// Returns how much gas was spent.
|
||||
fn spent(&self) -> T::Gas {
|
||||
self.limit - self.gas_left
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn tokens(&self) -> &[ErasedToken] {
|
||||
&self.tokens
|
||||
}
|
||||
}
|
||||
|
||||
/// Buy the given amount of gas.
|
||||
///
|
||||
/// Cost is calculated by multiplying the gas cost (taken from the storage) by the `gas_limit`.
|
||||
/// The funds are deducted from `transactor`.
|
||||
pub fn buy_gas<T: Trait>(
|
||||
transactor: &T::AccountId,
|
||||
gas_limit: T::Gas,
|
||||
) -> Result<(GasMeter<T>, NegativeImbalanceOf<T>), &'static str> {
|
||||
// Check if the specified amount of gas is available in the current block.
|
||||
// This cannot underflow since `gas_spent` is never greater than `block_gas_limit`.
|
||||
let gas_available = <Module<T>>::block_gas_limit() - <Module<T>>::gas_spent();
|
||||
if gas_limit > gas_available {
|
||||
// gas limit reached, revert the transaction and retry again in the future
|
||||
return Err(BLOCK_FULL);
|
||||
}
|
||||
|
||||
// Buy the specified amount of gas.
|
||||
let gas_price = <Module<T>>::gas_price();
|
||||
let cost = gas_limit.clone().into()
|
||||
.checked_mul(&gas_price)
|
||||
.ok_or("overflow multiplying gas limit by price")?;
|
||||
|
||||
let imbalance = T::Currency::withdraw(
|
||||
transactor,
|
||||
cost,
|
||||
WithdrawReason::Fee,
|
||||
ExistenceRequirement::KeepAlive
|
||||
)?;
|
||||
|
||||
Ok((GasMeter {
|
||||
limit: gas_limit,
|
||||
gas_left: gas_limit,
|
||||
gas_price,
|
||||
|
||||
#[cfg(test)]
|
||||
tokens: Vec::new(),
|
||||
}, imbalance))
|
||||
}
|
||||
|
||||
/// Refund the unused gas.
|
||||
pub fn refund_unused_gas<T: Trait>(
|
||||
transactor: &T::AccountId,
|
||||
gas_meter: GasMeter<T>,
|
||||
imbalance: NegativeImbalanceOf<T>,
|
||||
) {
|
||||
let gas_spent = gas_meter.spent();
|
||||
let gas_left = gas_meter.gas_left();
|
||||
|
||||
// Increase total spent gas.
|
||||
// This cannot overflow, since `gas_spent` is never greater than `block_gas_limit`, which
|
||||
// also has T::Gas type.
|
||||
<GasSpent<T>>::mutate(|block_gas_spent| *block_gas_spent += gas_spent);
|
||||
|
||||
// Refund gas left by the price it was bought at.
|
||||
let refund = gas_left.into() * gas_meter.gas_price;
|
||||
let refund_imbalance = T::Currency::deposit_creating(transactor, refund);
|
||||
if let Ok(imbalance) = imbalance.offset(refund_imbalance) {
|
||||
T::GasPayment::on_unbalanced(imbalance);
|
||||
}
|
||||
}
|
||||
|
||||
/// A little handy utility for converting a value in balance units into approximate value in gas units
|
||||
/// at the given gas price.
|
||||
pub fn approx_gas_for_balance<T: Trait>(gas_price: BalanceOf<T>, balance: BalanceOf<T>) -> T::Gas {
|
||||
(balance / gas_price).saturated_into::<T::Gas>()
|
||||
}
|
||||
|
||||
/// A simple utility macro that helps to match against a
|
||||
/// list of tokens.
|
||||
#[macro_export]
|
||||
macro_rules! match_tokens {
|
||||
($tokens_iter:ident,) => {
|
||||
};
|
||||
($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
|
||||
{
|
||||
let next = ($tokens_iter).next().unwrap();
|
||||
let pattern = $x;
|
||||
|
||||
// Note that we don't specify the type name directly in this macro,
|
||||
// we only have some expression $x of some type. At the same time, we
|
||||
// have an iterator of Box<dyn Any> and to downcast we need to specify
|
||||
// the type which we want downcast to.
|
||||
//
|
||||
// So what we do is we assign `_pattern_typed_next_ref` to the a variable which has
|
||||
// the required type.
|
||||
//
|
||||
// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
|
||||
// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
|
||||
|
||||
let mut _pattern_typed_next_ref = &pattern;
|
||||
_pattern_typed_next_ref = match next.token.downcast_ref() {
|
||||
Some(p) => {
|
||||
assert_eq!(p, &pattern);
|
||||
p
|
||||
}
|
||||
None => {
|
||||
panic!("expected type {} got {}", stringify!($x), next.description);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match_tokens!($tokens_iter, $($rest)*);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{GasMeter, Token};
|
||||
use crate::tests::Test;
|
||||
|
||||
/// A trivial token that charges 1 unit of gas.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct UnitToken;
|
||||
impl Token<Test> for UnitToken {
|
||||
type Metadata = ();
|
||||
fn calculate_amount(&self, _metadata: &()) -> u64 { 1 }
|
||||
}
|
||||
|
||||
struct DoubleTokenMetadata {
|
||||
multiplier: u64,
|
||||
}
|
||||
/// A simple token that charges for the given amount multiplied to
|
||||
/// a multiplier taken from a given metadata.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct DoubleToken(u64);
|
||||
|
||||
impl Token<Test> for DoubleToken {
|
||||
type Metadata = DoubleTokenMetadata;
|
||||
fn calculate_amount(&self, metadata: &DoubleTokenMetadata) -> u64 {
|
||||
// Probably you want to use saturating mul in production code.
|
||||
self.0 * metadata.multiplier
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let gas_meter = GasMeter::<Test>::with_limit(50000, 10);
|
||||
assert_eq!(gas_meter.gas_left(), 50000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let mut gas_meter = GasMeter::<Test>::with_limit(50000, 10);
|
||||
|
||||
let result = gas_meter.charge(&DoubleTokenMetadata { multiplier: 3 }, DoubleToken(10));
|
||||
assert!(!result.is_out_of_gas());
|
||||
|
||||
assert_eq!(gas_meter.gas_left(), 49_970);
|
||||
assert_eq!(gas_meter.spent(), 30);
|
||||
assert_eq!(gas_meter.gas_price(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracing() {
|
||||
let mut gas_meter = GasMeter::<Test>::with_limit(50000, 10);
|
||||
assert!(!gas_meter.charge(&(), UnitToken).is_out_of_gas());
|
||||
assert!(!gas_meter
|
||||
.charge(&DoubleTokenMetadata { multiplier: 3 }, DoubleToken(10))
|
||||
.is_out_of_gas());
|
||||
|
||||
let mut tokens = gas_meter.tokens()[0..2].iter();
|
||||
match_tokens!(tokens, UnitToken, DoubleToken(10),);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,820 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! # Contract Module
|
||||
//!
|
||||
//! The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts.
|
||||
//!
|
||||
//! - [`contract::Trait`](./trait.Trait.html)
|
||||
//! - [`Call`](./enum.Call.html)
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This module extends accounts based on the `Currency` trait to have smart-contract functionality. It can
|
||||
//! be used with other modules that implement accounts based on `Currency`. These "smart-contract accounts"
|
||||
//! have the ability to create smart-contracts and make calls to other contract and non-contract accounts.
|
||||
//!
|
||||
//! The smart-contract code is stored once in a `code_cache`, and later retrievable via its `code_hash`.
|
||||
//! This means that multiple smart-contracts can be instantiated from the same `code_cache`, without replicating
|
||||
//! the code each time.
|
||||
//!
|
||||
//! When a smart-contract is called, its associated code is retrieved via the code hash and gets executed.
|
||||
//! This call can alter the storage entries of the smart-contract account, create new smart-contracts,
|
||||
//! or call other smart-contracts.
|
||||
//!
|
||||
//! Finally, when an account is reaped, its associated code and storage of the smart-contract account
|
||||
//! will also be deleted.
|
||||
//!
|
||||
//! ### Gas
|
||||
//!
|
||||
//! Senders must specify a gas limit with every call, as all instructions invoked by the smart-contract require gas.
|
||||
//! Unused gas is refunded after the call, regardless of the execution outcome.
|
||||
//!
|
||||
//! If the gas limit is reached, then all calls and state changes (including balance transfers) are only
|
||||
//! reverted at the current call's contract level. For example, if contract A calls B and B runs out of gas mid-call,
|
||||
//! then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state
|
||||
//! changes still persist.
|
||||
//!
|
||||
//! ### Notable Scenarios
|
||||
//!
|
||||
//! Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up",
|
||||
//! and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
|
||||
//! fails, A can decide how to handle that failure, either proceeding or reverting A's changes.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Dispatchable functions
|
||||
//!
|
||||
//! * `put_code` - Stores the given binary Wasm code into the chain's storage and returns its `code_hash`.
|
||||
//! * `create` - Deploys a new contract from the given `code_hash`, optionally transferring some balance.
|
||||
//! This creates a new smart contract account and calls its contract deploy handler to initialize the contract.
|
||||
//! * `call` - Makes a call to an account, optionally transferring some balance.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! The Contract module is a work in progress. The following examples show how this Contract module can be
|
||||
//! used to create and call contracts.
|
||||
//!
|
||||
//! * [`pDSL`](https://github.com/Robbepop/pdsl) is a domain specific language that enables writing
|
||||
//! WebAssembly based smart contracts in the Rust programming language. This is a work in progress.
|
||||
//!
|
||||
//! ## Related Modules
|
||||
//!
|
||||
//! * [Balances](../srml_balances/index.html)
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[macro_use]
|
||||
mod gas;
|
||||
|
||||
mod account_db;
|
||||
mod exec;
|
||||
mod wasm;
|
||||
mod rent;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::exec::ExecutionContext;
|
||||
use crate::account_db::{AccountDb, DirectAccountDb};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
use substrate_primitives::crypto::UncheckedFrom;
|
||||
use rstd::{prelude::*, marker::PhantomData, convert::TryFrom};
|
||||
use parity_codec::{Codec, Encode, Decode};
|
||||
use runtime_io::blake2_256;
|
||||
use runtime_primitives::traits::{
|
||||
Hash, SimpleArithmetic, Bounded, StaticLookup, Zero, MaybeSerializeDebug, Member
|
||||
};
|
||||
use srml_support::dispatch::{Result, Dispatchable};
|
||||
use srml_support::{
|
||||
Parameter, StorageMap, StorageValue, decl_module, decl_event, decl_storage, storage::child
|
||||
};
|
||||
use srml_support::traits::{OnFreeBalanceZero, OnUnbalanced, Currency};
|
||||
use system::{ensure_signed, RawOrigin};
|
||||
use substrate_primitives::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX;
|
||||
use timestamp;
|
||||
|
||||
pub type CodeHash<T> = <T as system::Trait>::Hash;
|
||||
pub type TrieId = Vec<u8>;
|
||||
|
||||
/// A function that generates an `AccountId` for a contract upon instantiation.
|
||||
pub trait ContractAddressFor<CodeHash, AccountId> {
|
||||
fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId;
|
||||
}
|
||||
|
||||
/// A function that returns the fee for dispatching a `Call`.
|
||||
pub trait ComputeDispatchFee<Call, Balance> {
|
||||
fn compute_dispatch_fee(call: &Call) -> Balance;
|
||||
}
|
||||
|
||||
/// Information for managing an acocunt and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account
|
||||
#[derive(Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum ContractInfo<T: Trait> {
|
||||
Alive(AliveContractInfo<T>),
|
||||
Tombstone(TombstoneContractInfo<T>),
|
||||
}
|
||||
|
||||
impl<T: Trait> ContractInfo<T> {
|
||||
/// If contract is alive then return some alive info
|
||||
pub fn get_alive(self) -> Option<AliveContractInfo<T>> {
|
||||
if let ContractInfo::Alive(alive) = self {
|
||||
Some(alive)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// If contract is alive then return some reference to alive info
|
||||
pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {
|
||||
if let ContractInfo::Alive(ref alive) = self {
|
||||
Some(alive)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// If contract is alive then return some mutable reference to alive info
|
||||
pub fn as_alive_mut(&mut self) -> Option<&mut AliveContractInfo<T>> {
|
||||
if let ContractInfo::Alive(ref mut alive) = self {
|
||||
Some(alive)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// If contract is tombstone then return some alive info
|
||||
pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {
|
||||
if let ContractInfo::Tombstone(tombstone) = self {
|
||||
Some(tombstone)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// If contract is tombstone then return some reference to tombstone info
|
||||
pub fn as_tombstone(&self) -> Option<&TombstoneContractInfo<T>> {
|
||||
if let ContractInfo::Tombstone(ref tombstone) = self {
|
||||
Some(tombstone)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// If contract is tombstone then return some mutable reference to tombstone info
|
||||
pub fn as_tombstone_mut(&mut self) -> Option<&mut TombstoneContractInfo<T>> {
|
||||
if let ContractInfo::Tombstone(ref mut tombstone) = self {
|
||||
Some(tombstone)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type AliveContractInfo<T> =
|
||||
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as system::Trait>::BlockNumber>;
|
||||
|
||||
/// Information for managing an account and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account.
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
|
||||
/// Unique ID for the subtree encoded as a bytes vector.
|
||||
pub trie_id: TrieId,
|
||||
/// The size of stored value in octet.
|
||||
pub storage_size: u32,
|
||||
/// The code associated with a given account.
|
||||
pub code_hash: CodeHash,
|
||||
/// Pay rent at most up to this value.
|
||||
pub rent_allowance: Balance,
|
||||
/// Last block rent has been payed.
|
||||
pub deduct_block: BlockNumber,
|
||||
/// Last block child storage has been written.
|
||||
pub last_write: Option<BlockNumber>,
|
||||
}
|
||||
|
||||
pub type TombstoneContractInfo<T> =
|
||||
RawTombstoneContractInfo<<T as system::Trait>::Hash, <T as system::Trait>::Hashing>;
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Encode, Decode, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
|
||||
|
||||
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
|
||||
where
|
||||
H: Member + MaybeSerializeDebug + AsRef<[u8]> + AsMut<[u8]> + Copy + Default + rstd::hash::Hash
|
||||
+ Codec,
|
||||
Hasher: Hash<Output=H>,
|
||||
{
|
||||
fn new(storage_root: &[u8], code_hash: H) -> Self {
|
||||
let mut buf = Vec::new();
|
||||
storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));
|
||||
buf.extend_from_slice(code_hash.as_ref());
|
||||
RawTombstoneContractInfo(Hasher::hash(&buf[..]), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a trie id (trie id must be unique and collision resistant depending upon its context).
|
||||
/// Note that it is different than encode because trie id should be collision resistant
|
||||
/// (being a proper unique identifier).
|
||||
pub trait TrieIdGenerator<AccountId> {
|
||||
/// Get a trie id for an account, using reference to parent account trie id to ensure
|
||||
/// uniqueness of trie id.
|
||||
///
|
||||
/// The implementation must ensure every new trie id is unique: two consecutive calls with the
|
||||
/// same parameter needs to return different trie id values.
|
||||
///
|
||||
/// Also, the implementation is responsible for ensuring that `TrieId` starts with
|
||||
/// `:child_storage:`.
|
||||
/// TODO: We want to change this, see https://github.com/paritytech/substrate/issues/2325
|
||||
fn trie_id(account_id: &AccountId) -> TrieId;
|
||||
}
|
||||
|
||||
/// Get trie id from `account_id`.
|
||||
pub struct TrieIdFromParentCounter<T: Trait>(PhantomData<T>);
|
||||
|
||||
/// This generator uses inner counter for account id and applies the hash over `AccountId +
|
||||
/// accountid_counter`.
|
||||
impl<T: Trait> TrieIdGenerator<T::AccountId> for TrieIdFromParentCounter<T>
|
||||
where
|
||||
T::AccountId: AsRef<[u8]>
|
||||
{
|
||||
fn trie_id(account_id: &T::AccountId) -> TrieId {
|
||||
// Note that skipping a value due to error is not an issue here.
|
||||
// We only need uniqueness, not sequence.
|
||||
let new_seed = <AccountCounter<T>>::mutate(|v| {
|
||||
*v = v.wrapping_add(1);
|
||||
*v
|
||||
});
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(account_id.as_ref());
|
||||
buf.extend_from_slice(&new_seed.to_le_bytes()[..]);
|
||||
|
||||
// TODO: see https://github.com/paritytech/substrate/issues/2325
|
||||
CHILD_STORAGE_KEY_PREFIX.iter()
|
||||
.chain(b"default:")
|
||||
.chain(T::Hashing::hash(&buf[..]).as_ref().iter())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
|
||||
pub type NegativeImbalanceOf<T> =
|
||||
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
|
||||
|
||||
pub trait Trait: timestamp::Trait {
|
||||
type Currency: Currency<Self::AccountId>;
|
||||
|
||||
/// The outer call dispatch type.
|
||||
type Call: Parameter + Dispatchable<Origin=<Self as system::Trait>::Origin>;
|
||||
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
type Gas: Parameter + Default + Codec + SimpleArithmetic + Bounded + Copy +
|
||||
Into<BalanceOf<Self>> + TryFrom<BalanceOf<Self>>;
|
||||
|
||||
/// A function type to get the contract address given the creator.
|
||||
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
|
||||
|
||||
/// A function type that computes the fee for dispatching the given `Call`.
|
||||
///
|
||||
/// It is recommended (though not required) for this function to return a fee that would be taken
|
||||
/// by the Executive module for regular dispatch.
|
||||
type ComputeDispatchFee: ComputeDispatchFee<Self::Call, BalanceOf<Self>>;
|
||||
|
||||
/// trieid id generator
|
||||
type TrieIdGenerator: TrieIdGenerator<Self::AccountId>;
|
||||
|
||||
/// Handler for the unbalanced reduction when making a gas payment.
|
||||
type GasPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
||||
}
|
||||
|
||||
/// Simple contract address determiner.
|
||||
///
|
||||
/// Address calculated from the code (of the constructor), input data to the constructor,
|
||||
/// and the account id that requested the account creation.
|
||||
///
|
||||
/// Formula: `blake2_256(blake2_256(code) + blake2_256(data) + origin)`
|
||||
pub struct SimpleAddressDeterminator<T: Trait>(PhantomData<T>);
|
||||
impl<T: Trait> ContractAddressFor<CodeHash<T>, T::AccountId> for SimpleAddressDeterminator<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
{
|
||||
fn contract_address_for(code_hash: &CodeHash<T>, data: &[u8], origin: &T::AccountId) -> T::AccountId {
|
||||
let data_hash = T::Hashing::hash(data);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(code_hash.as_ref());
|
||||
buf.extend_from_slice(data_hash.as_ref());
|
||||
buf.extend_from_slice(origin.as_ref());
|
||||
|
||||
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf[..]))
|
||||
}
|
||||
}
|
||||
|
||||
/// The default dispatch fee computor computes the fee in the same way that
|
||||
/// the implementation of `MakePayment` for the Balances module does.
|
||||
pub struct DefaultDispatchFeeComputor<T: Trait>(PhantomData<T>);
|
||||
impl<T: Trait> ComputeDispatchFee<T::Call, BalanceOf<T>> for DefaultDispatchFeeComputor<T> {
|
||||
fn compute_dispatch_fee(call: &T::Call) -> BalanceOf<T> {
|
||||
let encoded_len = call.using_encoded(|encoded| encoded.len() as u32);
|
||||
let base_fee = <Module<T>>::transaction_base_fee();
|
||||
let byte_fee = <Module<T>>::transaction_byte_fee();
|
||||
base_fee + byte_fee * encoded_len.into()
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
/// Contracts module.
|
||||
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin {
|
||||
fn deposit_event<T>() = default;
|
||||
|
||||
/// Updates the schedule for metering contracts.
|
||||
///
|
||||
/// The schedule must have a greater version than the stored schedule.
|
||||
pub fn update_schedule(schedule: Schedule<T::Gas>) -> Result {
|
||||
if <Module<T>>::current_schedule().version >= schedule.version {
|
||||
return Err("new schedule must have a greater version than current");
|
||||
}
|
||||
|
||||
Self::deposit_event(RawEvent::ScheduleUpdated(schedule.version));
|
||||
<CurrentSchedule<T>>::put(schedule);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores the given binary Wasm code into the chain's storage and returns its `codehash`.
|
||||
/// You can instantiate contracts only with stored code.
|
||||
pub fn put_code(
|
||||
origin,
|
||||
#[compact] gas_limit: T::Gas,
|
||||
code: Vec<u8>
|
||||
) -> Result {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let schedule = <Module<T>>::current_schedule();
|
||||
|
||||
let (mut gas_meter, imbalance) = gas::buy_gas::<T>(&origin, gas_limit)?;
|
||||
|
||||
let result = wasm::save_code::<T>(code, &mut gas_meter, &schedule);
|
||||
if let Ok(code_hash) = result {
|
||||
Self::deposit_event(RawEvent::CodeStored(code_hash));
|
||||
}
|
||||
|
||||
gas::refund_unused_gas::<T>(&origin, gas_meter, imbalance);
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
/// Makes a call to an account, optionally transferring some balance.
|
||||
///
|
||||
/// * If the account is a smart-contract account, the associated code will be
|
||||
/// executed and any value will be transferred.
|
||||
/// * If the account is a regular account, any value will be transferred.
|
||||
/// * If no account exists and the call value is not less than `existential_deposit`,
|
||||
/// a regular account will be created and any value will be transferred.
|
||||
pub fn call(
|
||||
origin,
|
||||
dest: <T::Lookup as StaticLookup>::Source,
|
||||
#[compact] value: BalanceOf<T>,
|
||||
#[compact] gas_limit: T::Gas,
|
||||
data: Vec<u8>
|
||||
) -> Result {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
|
||||
// Pay for the gas upfront.
|
||||
//
|
||||
// NOTE: it is very important to avoid any state changes before
|
||||
// paying for the gas.
|
||||
let (mut gas_meter, imbalance) = gas::buy_gas::<T>(&origin, gas_limit)?;
|
||||
|
||||
let cfg = Config::preload();
|
||||
let vm = crate::wasm::WasmVm::new(&cfg.schedule);
|
||||
let loader = crate::wasm::WasmLoader::new(&cfg.schedule);
|
||||
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
|
||||
|
||||
let result = ctx.call(dest, value, &mut gas_meter, &data, exec::EmptyOutputBuf::new());
|
||||
|
||||
if let Ok(_) = result {
|
||||
// Commit all changes that made it thus far into the persistent storage.
|
||||
DirectAccountDb.commit(ctx.overlay.into_change_set());
|
||||
|
||||
// Then deposit all events produced.
|
||||
ctx.events.into_iter().for_each(|indexed_event| {
|
||||
<system::Module<T>>::deposit_event_indexed(
|
||||
&*indexed_event.topics,
|
||||
<T as Trait>::Event::from(indexed_event.event).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Refund cost of the unused gas.
|
||||
//
|
||||
// NOTE: This should go after the commit to the storage, since the storage changes
|
||||
// can alter the balance of the caller.
|
||||
gas::refund_unused_gas::<T>(&origin, gas_meter, imbalance);
|
||||
|
||||
// Dispatch every recorded call with an appropriate origin.
|
||||
ctx.calls.into_iter().for_each(|(who, call)| {
|
||||
let result = call.dispatch(RawOrigin::Signed(who.clone()).into());
|
||||
Self::deposit_event(RawEvent::Dispatched(who, result.is_ok()));
|
||||
});
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
/// Creates a new contract from the `codehash` generated by `put_code`, optionally transferring some balance.
|
||||
///
|
||||
/// Creation is executed as follows:
|
||||
///
|
||||
/// - The destination address is computed based on the sender and hash of the code.
|
||||
/// - The smart-contract account is created at the computed address.
|
||||
/// - The `ctor_code` is executed in the context of the newly-created account. Buffer returned
|
||||
/// after the execution is saved as the `code` of the account. That code will be invoked
|
||||
/// upon any call received by this account.
|
||||
/// - The contract is initialized.
|
||||
pub fn create(
|
||||
origin,
|
||||
#[compact] endowment: BalanceOf<T>,
|
||||
#[compact] gas_limit: T::Gas,
|
||||
code_hash: CodeHash<T>,
|
||||
data: Vec<u8>
|
||||
) -> Result {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
// Commit the gas upfront.
|
||||
//
|
||||
// NOTE: It is very important to avoid any state changes before
|
||||
// paying for the gas.
|
||||
let (mut gas_meter, imbalance) = gas::buy_gas::<T>(&origin, gas_limit)?;
|
||||
|
||||
let cfg = Config::preload();
|
||||
let vm = crate::wasm::WasmVm::new(&cfg.schedule);
|
||||
let loader = crate::wasm::WasmLoader::new(&cfg.schedule);
|
||||
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
|
||||
let result = ctx.instantiate(endowment, &mut gas_meter, &code_hash, &data);
|
||||
|
||||
if let Ok(_) = result {
|
||||
// Commit all changes that made it thus far into the persistent storage.
|
||||
DirectAccountDb.commit(ctx.overlay.into_change_set());
|
||||
|
||||
// Then deposit all events produced.
|
||||
ctx.events.into_iter().for_each(|indexed_event| {
|
||||
<system::Module<T>>::deposit_event_indexed(
|
||||
&*indexed_event.topics,
|
||||
<T as Trait>::Event::from(indexed_event.event).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Refund cost of the unused gas.
|
||||
//
|
||||
// NOTE: This should go after the commit to the storage, since the storage changes
|
||||
// can alter the balance of the caller.
|
||||
gas::refund_unused_gas::<T>(&origin, gas_meter, imbalance);
|
||||
|
||||
// Dispatch every recorded call with an appropriate origin.
|
||||
ctx.calls.into_iter().for_each(|(who, call)| {
|
||||
let result = call.dispatch(RawOrigin::Signed(who.clone()).into());
|
||||
Self::deposit_event(RawEvent::Dispatched(who, result.is_ok()));
|
||||
});
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
/// Allows block producers to claim a small reward for evicting a contract. If a block producer
|
||||
/// fails to do so, a regular users will be allowed to claim the reward.
|
||||
///
|
||||
/// If contract is not evicted as a result of this call, no actions are taken and
|
||||
/// the sender is not eligible for the reward.
|
||||
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
|
||||
let origin = origin.into();
|
||||
let (signed, rewarded) = match origin {
|
||||
Ok(system::RawOrigin::Signed(ref account)) if aux_sender.is_none() => {
|
||||
(true, account)
|
||||
},
|
||||
Ok(system::RawOrigin::None) if aux_sender.is_some() => {
|
||||
(false, aux_sender.as_ref().expect("checked above"))
|
||||
},
|
||||
_ => return Err("Invalid surcharge claim: origin must be signed or \
|
||||
inherent and auxiliary sender only provided on inherent")
|
||||
};
|
||||
|
||||
// Add some advantage for block producers (who send unsigned extrinsics) by
|
||||
// adding a handicap: for signed extrinsics we use a slightly older block number
|
||||
// for the eviction check. This can be viewed as if we pushed regular users back in past.
|
||||
let handicap = if signed {
|
||||
<Module<T>>::signed_claim_handicap()
|
||||
} else {
|
||||
Zero::zero()
|
||||
};
|
||||
|
||||
// If poking the contract has lead to eviction of the contract, give out the rewards.
|
||||
if rent::try_evict::<T>(&dest, handicap) == rent::RentOutcome::Evicted {
|
||||
T::Currency::deposit_into_existing(rewarded, Self::surcharge_reward())?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows a contract to restore a tombstone by giving its storage.
|
||||
///
|
||||
/// The contract that wants to restore (i.e. origin of the call, or `msg.sender` in Solidity terms) will compute a
|
||||
/// tombstone with its storage and the given code_hash. If the computed tombstone
|
||||
/// match the destination one, the destination contract is restored with the rent_allowance` specified,
|
||||
/// while the origin sends all its funds to the destination and is removed.
|
||||
fn restore_to(
|
||||
origin,
|
||||
dest: T::AccountId,
|
||||
code_hash: CodeHash<T>,
|
||||
rent_allowance: BalanceOf<T>,
|
||||
delta: Vec<exec::StorageKey>
|
||||
) {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
|
||||
.and_then(|c| c.get_alive())
|
||||
.ok_or("Cannot restore from inexisting or tombstone contract")?;
|
||||
|
||||
let current_block = <system::Module<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err("Origin TrieId written in the current block");
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
|
||||
.and_then(|c| c.get_tombstone())
|
||||
.ok_or("Cannot restore to inexisting or alive contract")?;
|
||||
|
||||
let last_write = if !delta.is_empty() {
|
||||
Some(current_block)
|
||||
} else {
|
||||
origin_contract.last_write
|
||||
};
|
||||
|
||||
let key_values_taken = delta.iter()
|
||||
.filter_map(|key| {
|
||||
child::get_raw(&origin_contract.trie_id, &blake2_256(key)).map(|value| {
|
||||
child::kill(&origin_contract.trie_id, &blake2_256(key));
|
||||
(key, value)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
// This operation is cheap enough because last_write (delta not included)
|
||||
// is not this block as it has been checked earlier.
|
||||
&runtime_io::child_storage_root(&origin_contract.trie_id)[..],
|
||||
code_hash,
|
||||
);
|
||||
|
||||
if tombstone != dest_tombstone {
|
||||
for (key, value) in key_values_taken {
|
||||
child::put_raw(&origin_contract.trie_id, &blake2_256(key), &value);
|
||||
}
|
||||
|
||||
return Err("Tombstones don't match");
|
||||
}
|
||||
|
||||
origin_contract.storage_size -= key_values_taken.iter()
|
||||
.map(|(_, value)| value.len() as u32)
|
||||
.sum::<u32>();
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(RawAliveContractInfo {
|
||||
trie_id: origin_contract.trie_id,
|
||||
storage_size: origin_contract.storage_size,
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
deduct_block: current_block,
|
||||
last_write,
|
||||
}));
|
||||
|
||||
let origin_free_balance = T::Currency::free_balance(&origin);
|
||||
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
|
||||
T::Currency::deposit_creating(&dest, origin_free_balance);
|
||||
}
|
||||
|
||||
fn on_finalize() {
|
||||
<GasSpent<T>>::kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_event! {
|
||||
pub enum Event<T>
|
||||
where
|
||||
Balance = BalanceOf<T>,
|
||||
<T as system::Trait>::AccountId,
|
||||
<T as system::Trait>::Hash
|
||||
{
|
||||
/// Transfer happened `from` to `to` with given `value` as part of a `call` or `create`.
|
||||
Transfer(AccountId, AccountId, Balance),
|
||||
|
||||
/// Contract deployed by address at the specified address.
|
||||
Instantiated(AccountId, AccountId),
|
||||
|
||||
/// Code with the specified hash has been stored.
|
||||
CodeStored(Hash),
|
||||
|
||||
/// 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 from contract of account.
|
||||
Contract(AccountId, Vec<u8>),
|
||||
}
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Contract {
|
||||
/// Number of block delay an extrinsic claim surcharge has.
|
||||
///
|
||||
/// When claim surchage is called by an extrinsic the rent is checked
|
||||
/// for current_block - delay
|
||||
SignedClaimHandicap get(signed_claim_handicap) config(): T::BlockNumber;
|
||||
/// The minimum amount required to generate a tombstone.
|
||||
TombstoneDeposit get(tombstone_deposit) config(): BalanceOf<T>;
|
||||
/// Size of a contract at the time of creation. This is a simple way to ensure
|
||||
/// that empty contracts eventually gets deleted.
|
||||
StorageSizeOffset get(storage_size_offset) config(): u32;
|
||||
/// Price of a byte of storage per one block interval. Should be greater than 0.
|
||||
RentByteFee get(rent_byte_price) config(): BalanceOf<T>;
|
||||
/// The amount of funds a contract should deposit in order to offset
|
||||
/// the cost of one byte.
|
||||
///
|
||||
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
|
||||
/// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
|
||||
/// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
|
||||
/// then it would pay 500 BU/day.
|
||||
RentDepositOffset get(rent_deposit_offset) config(): BalanceOf<T>;
|
||||
/// Reward that is received by the party whose touch has led
|
||||
/// to removal of a contract.
|
||||
SurchargeReward get(surcharge_reward) config(): BalanceOf<T>;
|
||||
/// The fee required to make a transfer.
|
||||
TransferFee get(transfer_fee) config(): BalanceOf<T>;
|
||||
/// The fee required to create an account.
|
||||
CreationFee get(creation_fee) config(): BalanceOf<T>;
|
||||
/// The fee to be paid for making a transaction; the base.
|
||||
TransactionBaseFee get(transaction_base_fee) config(): BalanceOf<T>;
|
||||
/// The fee to be paid for making a transaction; the per-byte portion.
|
||||
TransactionByteFee get(transaction_byte_fee) config(): BalanceOf<T>;
|
||||
/// The fee required to create a contract instance.
|
||||
ContractFee get(contract_fee) config(): BalanceOf<T> = 21.into();
|
||||
/// The base fee charged for calling into a contract.
|
||||
CallBaseFee get(call_base_fee) config(): T::Gas = 135.into();
|
||||
/// The base fee charged for creating a contract.
|
||||
CreateBaseFee get(create_base_fee) config(): T::Gas = 175.into();
|
||||
/// The price of one unit of gas.
|
||||
GasPrice get(gas_price) config(): BalanceOf<T> = 1.into();
|
||||
/// The maximum nesting level of a call/create stack.
|
||||
MaxDepth get(max_depth) config(): u32 = 100;
|
||||
/// The maximum amount of gas that could be expended per block.
|
||||
BlockGasLimit get(block_gas_limit) config(): T::Gas = 10_000_000.into();
|
||||
/// Gas spent so far in this block.
|
||||
GasSpent get(gas_spent): T::Gas;
|
||||
/// Current cost schedule for contracts.
|
||||
CurrentSchedule get(current_schedule) config(): Schedule<T::Gas> = Schedule::default();
|
||||
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
||||
pub PristineCode: map CodeHash<T> => Option<Vec<u8>>;
|
||||
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
|
||||
pub CodeStorage: map CodeHash<T> => Option<wasm::PrefabWasmModule>;
|
||||
/// The subtrie counter.
|
||||
pub AccountCounter: u64 = 0;
|
||||
/// The code associated with a given account.
|
||||
pub ContractInfoOf: map T::AccountId => Option<ContractInfo<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
||||
fn on_free_balance_zero(who: &T::AccountId) {
|
||||
if let Some(ContractInfo::Alive(info)) = <ContractInfoOf<T>>::get(who) {
|
||||
child::kill_storage(&info.trie_id);
|
||||
}
|
||||
<ContractInfoOf<T>>::remove(who);
|
||||
}
|
||||
}
|
||||
|
||||
/// In-memory cache of configuration values.
|
||||
///
|
||||
/// We assume that these values can't be changed in the
|
||||
/// course of transaction execution.
|
||||
pub struct Config<T: Trait> {
|
||||
pub schedule: Schedule<T::Gas>,
|
||||
pub existential_deposit: BalanceOf<T>,
|
||||
pub max_depth: u32,
|
||||
pub contract_account_instantiate_fee: BalanceOf<T>,
|
||||
pub account_create_fee: BalanceOf<T>,
|
||||
pub transfer_fee: BalanceOf<T>,
|
||||
pub call_base_fee: T::Gas,
|
||||
pub instantiate_base_fee: T::Gas,
|
||||
}
|
||||
|
||||
impl<T: Trait> Config<T> {
|
||||
fn preload() -> Config<T> {
|
||||
Config {
|
||||
schedule: <Module<T>>::current_schedule(),
|
||||
existential_deposit: T::Currency::minimum_balance(),
|
||||
max_depth: <Module<T>>::max_depth(),
|
||||
contract_account_instantiate_fee: <Module<T>>::contract_fee(),
|
||||
account_create_fee: <Module<T>>::creation_fee(),
|
||||
transfer_fee: <Module<T>>::transfer_fee(),
|
||||
call_base_fee: <Module<T>>::call_base_fee(),
|
||||
instantiate_base_fee: <Module<T>>::create_base_fee(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of the cost schedule and other parameterizations for wasm vm.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq)]
|
||||
pub struct Schedule<Gas> {
|
||||
/// Version of the schedule.
|
||||
pub version: u32,
|
||||
|
||||
/// Cost of putting a byte of code into storage.
|
||||
pub put_code_per_byte_cost: Gas,
|
||||
|
||||
/// Gas cost of a growing memory by single page.
|
||||
pub grow_mem_cost: Gas,
|
||||
|
||||
/// Gas cost of a regular operation.
|
||||
pub regular_op_cost: Gas,
|
||||
|
||||
/// Gas cost per one byte returned.
|
||||
pub return_data_per_byte_cost: Gas,
|
||||
|
||||
/// Gas cost to deposit an event; the per-byte portion.
|
||||
pub event_data_per_byte_cost: Gas,
|
||||
|
||||
/// Gas cost to deposit an event; the cost per topic.
|
||||
pub event_per_topic_cost: Gas,
|
||||
|
||||
/// Gas cost to deposit an event; the base.
|
||||
pub event_base_cost: Gas,
|
||||
|
||||
/// Gas cost per one byte read from the sandbox memory.
|
||||
pub sandbox_data_read_cost: Gas,
|
||||
|
||||
/// Gas cost per one byte written to the sandbox memory.
|
||||
pub sandbox_data_write_cost: Gas,
|
||||
|
||||
/// The maximum number of topics supported by an event.
|
||||
pub max_event_topics: u32,
|
||||
|
||||
/// Maximum allowed stack height.
|
||||
///
|
||||
/// See https://wiki.parity.io/WebAssembly-StackHeight to find out
|
||||
/// how the stack frame cost is calculated.
|
||||
pub max_stack_height: u32,
|
||||
|
||||
/// Maximum number of memory pages allowed for a contract.
|
||||
pub max_memory_pages: u32,
|
||||
|
||||
/// Whether the `ext_println` function is allowed to be used contracts.
|
||||
/// MUST only be enabled for `dev` chains, NOT for production chains
|
||||
pub enable_println: bool,
|
||||
|
||||
/// The maximum length of a subject used for PRNG generation.
|
||||
pub max_subject_len: u32,
|
||||
}
|
||||
|
||||
impl<Gas: From<u32>> Default for Schedule<Gas> {
|
||||
fn default() -> Schedule<Gas> {
|
||||
Schedule {
|
||||
version: 0,
|
||||
put_code_per_byte_cost: 1.into(),
|
||||
grow_mem_cost: 1.into(),
|
||||
regular_op_cost: 1.into(),
|
||||
return_data_per_byte_cost: 1.into(),
|
||||
event_data_per_byte_cost: 1.into(),
|
||||
event_per_topic_cost: 1.into(),
|
||||
event_base_cost: 1.into(),
|
||||
sandbox_data_read_cost: 1.into(),
|
||||
sandbox_data_write_cost: 1.into(),
|
||||
max_event_topics: 4,
|
||||
max_stack_height: 64 * 1024,
|
||||
max_memory_pages: 16,
|
||||
enable_println: false,
|
||||
max_subject_len: 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{BalanceOf, ContractInfo, ContractInfoOf, Module, TombstoneContractInfo, Trait};
|
||||
use runtime_primitives::traits::{Bounded, CheckedDiv, CheckedMul, Saturating, Zero,
|
||||
SaturatedConversion};
|
||||
use srml_support::traits::{Currency, ExistenceRequirement, Imbalance, WithdrawReason};
|
||||
use srml_support::StorageMap;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
#[must_use]
|
||||
pub enum RentOutcome {
|
||||
/// Exempted from rent iff:
|
||||
/// * rent is offset completely by the `rent_deposit_offset`,
|
||||
/// * or rent has already been paid for this block number,
|
||||
/// * or account doesn't have a contract,
|
||||
/// * or account has a tombstone.
|
||||
Exempted,
|
||||
/// Evicted iff:
|
||||
/// * rent exceed rent allowance,
|
||||
/// * or can't withdraw the rent,
|
||||
/// * or go below subsistence threshold.
|
||||
Evicted,
|
||||
/// The outstanding dues were paid or were able to be paid.
|
||||
Ok,
|
||||
}
|
||||
|
||||
/// Evict and optionally pay dues (or check account can pay them otherwise) at the current
|
||||
/// block number (modulo `handicap`, read on).
|
||||
///
|
||||
/// `pay_rent` gives an ability to pay or skip paying rent.
|
||||
/// `handicap` gives a way to check or pay the rent up to a moment in the past instead
|
||||
/// of current block.
|
||||
///
|
||||
/// NOTE: This function acts eagerly, all modification are committed into the storage.
|
||||
fn try_evict_or_and_pay_rent<T: Trait>(
|
||||
account: &T::AccountId,
|
||||
handicap: T::BlockNumber,
|
||||
pay_rent: bool,
|
||||
) -> RentOutcome {
|
||||
let contract = match <ContractInfoOf<T>>::get(account) {
|
||||
None | Some(ContractInfo::Tombstone(_)) => return RentOutcome::Exempted,
|
||||
Some(ContractInfo::Alive(contract)) => contract,
|
||||
};
|
||||
|
||||
let current_block_number = <system::Module<T>>::block_number();
|
||||
|
||||
// How much block has passed since the last deduction for the contract.
|
||||
let blocks_passed = {
|
||||
// Calculate an effective block number, i.e. after adjusting for handicap.
|
||||
let effective_block_number = current_block_number.saturating_sub(handicap);
|
||||
let n = effective_block_number.saturating_sub(contract.deduct_block);
|
||||
if n.is_zero() {
|
||||
// Rent has already been paid
|
||||
return RentOutcome::Exempted;
|
||||
}
|
||||
n
|
||||
};
|
||||
|
||||
let balance = T::Currency::free_balance(account);
|
||||
|
||||
// An amount of funds to charge per block for storage taken up by the contract.
|
||||
let fee_per_block = {
|
||||
let free_storage = balance
|
||||
.checked_div(&<Module<T>>::rent_deposit_offset())
|
||||
.unwrap_or_else(Zero::zero);
|
||||
|
||||
let effective_storage_size =
|
||||
<BalanceOf<T>>::from(contract.storage_size).saturating_sub(free_storage);
|
||||
|
||||
effective_storage_size
|
||||
.checked_mul(&<Module<T>>::rent_byte_price())
|
||||
.unwrap_or(<BalanceOf<T>>::max_value())
|
||||
};
|
||||
|
||||
if fee_per_block.is_zero() {
|
||||
// The rent deposit offset reduced the fee to 0. This means that the contract
|
||||
// gets the rent for free.
|
||||
return RentOutcome::Exempted;
|
||||
}
|
||||
|
||||
// The minimal amount of funds required for a contract not to be evicted.
|
||||
let subsistence_threshold = T::Currency::minimum_balance() + <Module<T>>::tombstone_deposit();
|
||||
|
||||
let dues = fee_per_block
|
||||
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
|
||||
.unwrap_or(<BalanceOf<T>>::max_value());
|
||||
|
||||
let dues_limited = dues.min(contract.rent_allowance);
|
||||
let rent_allowance_exceeded = dues > contract.rent_allowance;
|
||||
let is_below_subsistence = balance < subsistence_threshold;
|
||||
let go_below_subsistence = balance.saturating_sub(dues_limited) < subsistence_threshold;
|
||||
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
|
||||
account,
|
||||
dues_limited,
|
||||
WithdrawReason::Fee,
|
||||
balance.saturating_sub(dues_limited),
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
if !rent_allowance_exceeded && can_withdraw_rent && !go_below_subsistence {
|
||||
// Collect dues
|
||||
|
||||
if pay_rent {
|
||||
let imbalance = T::Currency::withdraw(
|
||||
account,
|
||||
dues,
|
||||
WithdrawReason::Fee,
|
||||
ExistenceRequirement::KeepAlive,
|
||||
)
|
||||
.expect(
|
||||
"Withdraw has been checked above;
|
||||
go_below_subsistence is false and subsistence > existencial_deposit;
|
||||
qed",
|
||||
);
|
||||
|
||||
<ContractInfoOf<T>>::mutate(account, |contract| {
|
||||
let contract = contract
|
||||
.as_mut()
|
||||
.and_then(|c| c.as_alive_mut())
|
||||
.expect("Dead or inexistent account has been exempt above; qed");
|
||||
|
||||
contract.rent_allowance -= imbalance.peek(); // rent_allowance is not exceeded
|
||||
contract.deduct_block = current_block_number;
|
||||
})
|
||||
}
|
||||
|
||||
RentOutcome::Ok
|
||||
} else {
|
||||
// Evict
|
||||
|
||||
if can_withdraw_rent && !go_below_subsistence {
|
||||
T::Currency::withdraw(
|
||||
account,
|
||||
dues,
|
||||
WithdrawReason::Fee,
|
||||
ExistenceRequirement::KeepAlive,
|
||||
)
|
||||
.expect("Can withdraw and don't go below subsistence");
|
||||
} else if !is_below_subsistence {
|
||||
T::Currency::make_free_balance_be(account, subsistence_threshold);
|
||||
} else {
|
||||
T::Currency::make_free_balance_be(account, <BalanceOf<T>>::zero());
|
||||
}
|
||||
|
||||
if !is_below_subsistence {
|
||||
// The contract has funds above subsistence deposit and that means it can afford to
|
||||
// leave tombstone.
|
||||
|
||||
// Note: this operation is heavy.
|
||||
let child_storage_root = runtime_io::child_storage_root(&contract.trie_id);
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
&child_storage_root[..],
|
||||
contract.code_hash,
|
||||
);
|
||||
<ContractInfoOf<T>>::insert(account, ContractInfo::Tombstone(tombstone));
|
||||
runtime_io::kill_child_storage(&contract.trie_id);
|
||||
}
|
||||
|
||||
RentOutcome::Evicted
|
||||
}
|
||||
}
|
||||
|
||||
/// Make account paying the rent for the current block number
|
||||
///
|
||||
/// NOTE: This function acts eagerly.
|
||||
pub fn pay_rent<T: Trait>(account: &T::AccountId) {
|
||||
let _ = try_evict_or_and_pay_rent::<T>(account, Zero::zero(), true);
|
||||
}
|
||||
|
||||
/// Evict the account if it should be evicted at the given block number.
|
||||
///
|
||||
/// `handicap` gives a way to check or pay the rent up to a moment in the past instead
|
||||
/// of current block. E.g. if the contract is going to be evicted at the current block,
|
||||
/// `handicap=1` can defer the eviction for 1 block.
|
||||
///
|
||||
/// NOTE: This function acts eagerly.
|
||||
pub fn try_evict<T: Trait>(account: &T::AccountId, handicap: T::BlockNumber) -> RentOutcome {
|
||||
try_evict_or_and_pay_rent::<T>(account, handicap, false)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that implements instrumented code cache.
|
||||
//!
|
||||
//! - In order to run contract code we need to instrument it with gas metering.
|
||||
//! To do that we need to provide the schedule which will supply exact gas costs values.
|
||||
//! We cache this code in the storage saving the schedule version.
|
||||
//! - Before running contract code we check if the cached code has the schedule version that
|
||||
//! is equal to the current saved schedule.
|
||||
//! If it is equal then run the code, if it isn't reinstrument with the current schedule.
|
||||
//! - When we update the schedule we want it to have strictly greater version than the current saved one:
|
||||
//! this guarantees that every instrumented contract code in cache cannot have the version equal to the current one.
|
||||
//! Thus, before executing a contract it should be reinstrument with new schedule.
|
||||
|
||||
use crate::gas::{GasMeter, Token};
|
||||
use crate::wasm::{prepare, runtime::Env, PrefabWasmModule};
|
||||
use crate::{CodeHash, CodeStorage, PristineCode, Schedule, Trait};
|
||||
use rstd::prelude::*;
|
||||
use runtime_primitives::traits::{CheckedMul, Hash, Bounded};
|
||||
use srml_support::StorageMap;
|
||||
|
||||
/// Gas metering token that used for charging storing code into the code storage.
|
||||
///
|
||||
/// Specifies the code length in bytes.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PutCodeToken(u32);
|
||||
|
||||
impl<T: Trait> Token<T> for PutCodeToken {
|
||||
type Metadata = Schedule<T::Gas>;
|
||||
|
||||
fn calculate_amount(&self, metadata: &Schedule<T::Gas>) -> T::Gas {
|
||||
metadata
|
||||
.put_code_per_byte_cost
|
||||
.checked_mul(&self.0.into())
|
||||
.unwrap_or_else(|| Bounded::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
/// Put code in the storage. The hash of code is used as a key and is returned
|
||||
/// as a result of this function.
|
||||
///
|
||||
/// This function instruments the given code and caches it in the storage.
|
||||
pub fn save<T: Trait>(
|
||||
original_code: Vec<u8>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
schedule: &Schedule<T::Gas>,
|
||||
) -> Result<CodeHash<T>, &'static str> {
|
||||
// The first time instrumentation is on the user. However, consequent reinstrumentation
|
||||
// due to the schedule changes is on governance system.
|
||||
if gas_meter
|
||||
.charge(schedule, PutCodeToken(original_code.len() as u32))
|
||||
.is_out_of_gas()
|
||||
{
|
||||
return Err("there is not enough gas for storing the code");
|
||||
}
|
||||
|
||||
let prefab_module = prepare::prepare_contract::<T, Env>(&original_code, schedule)?;
|
||||
let code_hash = T::Hashing::hash(&original_code);
|
||||
|
||||
<CodeStorage<T>>::insert(code_hash, prefab_module);
|
||||
<PristineCode<T>>::insert(code_hash, original_code);
|
||||
|
||||
Ok(code_hash)
|
||||
}
|
||||
|
||||
/// Load code with the given code hash.
|
||||
///
|
||||
/// If the module was instrumented with a lower version of schedule than
|
||||
/// the current one given as an argument, then this function will perform
|
||||
/// re-instrumentation and update the cache in the storage.
|
||||
pub fn load<T: Trait>(
|
||||
code_hash: &CodeHash<T>,
|
||||
schedule: &Schedule<T::Gas>,
|
||||
) -> Result<PrefabWasmModule, &'static str> {
|
||||
let mut prefab_module =
|
||||
<CodeStorage<T>>::get(code_hash).ok_or_else(|| "code is not found")?;
|
||||
|
||||
if prefab_module.schedule_version < schedule.version {
|
||||
// The current schedule version is greater than the version of the one cached
|
||||
// in the storage.
|
||||
//
|
||||
// We need to re-instrument the code with the latest schedule here.
|
||||
let original_code =
|
||||
<PristineCode<T>>::get(code_hash).ok_or_else(|| "pristine code is not found")?;
|
||||
prefab_module = prepare::prepare_contract::<T, Env>(&original_code, schedule)?;
|
||||
<CodeStorage<T>>::insert(code_hash, prefab_module.clone());
|
||||
}
|
||||
Ok(prefab_module)
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Definition of macros that hides boilerplate of defining external environment
|
||||
//! for a wasm module.
|
||||
//!
|
||||
//! Most likely you should use `define_env` macro.
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! convert_args {
|
||||
() => (vec![]);
|
||||
( $( $t:ty ),* ) => ( vec![ $( { use $crate::wasm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! gen_signature {
|
||||
( ( $( $params: ty ),* ) ) => (
|
||||
{
|
||||
parity_wasm::elements::FunctionType::new(convert_args!($($params),*), None)
|
||||
}
|
||||
);
|
||||
|
||||
( ( $( $params: ty ),* ) -> $returns: ty ) => (
|
||||
{
|
||||
parity_wasm::elements::FunctionType::new(convert_args!($($params),*), Some({
|
||||
use $crate::wasm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE
|
||||
}))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! gen_signature_dispatch {
|
||||
(
|
||||
$needle_name:ident,
|
||||
$needle_sig:ident ;
|
||||
$name:ident
|
||||
( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)* ) => {
|
||||
if stringify!($name).as_bytes() == $needle_name {
|
||||
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
|
||||
if $needle_sig == &signature {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
gen_signature_dispatch!($needle_name, $needle_sig ; $($rest)*);
|
||||
}
|
||||
};
|
||||
( $needle_name:ident, $needle_sig:ident ; ) => {
|
||||
};
|
||||
}
|
||||
|
||||
/// Unmarshall arguments and then execute `body` expression and return its result.
|
||||
macro_rules! unmarshall_then_body {
|
||||
( $body:tt, $ctx:ident, $args_iter:ident, $( $names:ident : $params:ty ),* ) => ({
|
||||
$(
|
||||
let $names : <$params as $crate::wasm::env_def::ConvertibleToWasm>::NativeType =
|
||||
$args_iter.next()
|
||||
.and_then(|v| <$params as $crate::wasm::env_def::ConvertibleToWasm>
|
||||
::from_typed_value(v.clone()))
|
||||
.expect(
|
||||
"precondition: all imports should be checked against the signatures of corresponding
|
||||
functions defined by `define_env!` macro by the user of the macro;
|
||||
signatures of these functions defined by `$params`;
|
||||
calls always made with arguments types of which are defined by the corresponding imports;
|
||||
thus types of arguments should be equal to type list in `$params` and
|
||||
length of argument list and $params should be equal;
|
||||
thus this can never be `None`;
|
||||
qed;
|
||||
"
|
||||
);
|
||||
)*
|
||||
$body
|
||||
})
|
||||
}
|
||||
|
||||
/// Since we can't specify the type of closure directly at binding site:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// let f: FnOnce() -> Result<<u32 as ConvertibleToWasm>::NativeType, _> = || { /* ... */ };
|
||||
/// ```
|
||||
///
|
||||
/// we use this function to constrain the type of the closure.
|
||||
#[inline(always)]
|
||||
pub fn constrain_closure<R, F>(f: F) -> F
|
||||
where
|
||||
F: FnOnce() -> Result<R, sandbox::HostError>,
|
||||
{
|
||||
f
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unmarshall_then_body_then_marshall {
|
||||
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
|
||||
let body = $crate::wasm::env_def::macros::constrain_closure::<
|
||||
<$returns as $crate::wasm::env_def::ConvertibleToWasm>::NativeType, _
|
||||
>(|| {
|
||||
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
let r = body()?;
|
||||
return Ok(sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() }))
|
||||
});
|
||||
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
|
||||
let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| {
|
||||
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
body()?;
|
||||
return Ok(sandbox::ReturnValue::Unit)
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! define_func {
|
||||
( < E: $ext_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
|
||||
fn $name< E: $ext_ty >(
|
||||
$ctx: &mut $crate::wasm::Runtime<E>,
|
||||
args: &[sandbox::TypedValue],
|
||||
) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
#[allow(unused)]
|
||||
let mut args = args.iter();
|
||||
|
||||
unmarshall_then_body_then_marshall!(
|
||||
args,
|
||||
$ctx,
|
||||
( $( $names : $params ),* ) $( -> $returns )* => $body
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_func {
|
||||
( $reg_cb:ident, < E: $ext_ty:tt > ; ) => {};
|
||||
|
||||
( $reg_cb:ident, < E: $ext_ty:tt > ;
|
||||
$name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt $($rest:tt)*
|
||||
) => {
|
||||
$reg_cb(
|
||||
stringify!($name).as_bytes(),
|
||||
{
|
||||
define_func!(
|
||||
< E: $ext_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
|
||||
);
|
||||
$name::<E>
|
||||
}
|
||||
);
|
||||
register_func!( $reg_cb, < E: $ext_ty > ; $($rest)* );
|
||||
};
|
||||
}
|
||||
|
||||
/// Define a function set that can be imported by executing wasm code.
|
||||
///
|
||||
/// **NB**: Be advised that all functions defined by this macro
|
||||
/// will panic if called with unexpected arguments.
|
||||
///
|
||||
/// It's up to the user of this macro to check signatures of wasm code to be executed
|
||||
/// and reject the code if any imported function has a mismatched signature.
|
||||
macro_rules! define_env {
|
||||
( $init_name:ident , < E: $ext_ty:tt > ,
|
||||
$( $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt , )*
|
||||
) => {
|
||||
pub struct $init_name;
|
||||
|
||||
impl $crate::wasm::env_def::ImportSatisfyCheck for $init_name {
|
||||
fn can_satisfy(name: &[u8], func_type: &parity_wasm::elements::FunctionType) -> bool {
|
||||
gen_signature_dispatch!( name, func_type ; $( $name ( $ctx $(, $names : $params )* ) $( -> $returns )* , )* );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Ext> $crate::wasm::env_def::FunctionImplProvider<E> for $init_name {
|
||||
fn impls<F: FnMut(&[u8], $crate::wasm::env_def::HostFunc<E>)>(f: &mut F) {
|
||||
register_func!(f, < E: $ext_ty > ; $( $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )* );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use parity_wasm::elements::FunctionType;
|
||||
use parity_wasm::elements::ValueType;
|
||||
use runtime_primitives::traits::Zero;
|
||||
use sandbox::{self, ReturnValue, TypedValue};
|
||||
use crate::wasm::tests::MockExt;
|
||||
use crate::wasm::Runtime;
|
||||
use crate::exec::Ext;
|
||||
use crate::Trait;
|
||||
|
||||
#[test]
|
||||
fn macro_unmarshall_then_body_then_marshall_value_or_trap() {
|
||||
fn test_value(
|
||||
_ctx: &mut u32,
|
||||
args: &[sandbox::TypedValue],
|
||||
) -> Result<ReturnValue, sandbox::HostError> {
|
||||
let mut args = args.iter();
|
||||
unmarshall_then_body_then_marshall!(
|
||||
args,
|
||||
_ctx,
|
||||
(a: u32, b: u32) -> u32 => {
|
||||
if b == 0 {
|
||||
Err(sandbox::HostError)
|
||||
} else {
|
||||
Ok(a / b)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let ctx = &mut 0;
|
||||
assert_eq!(
|
||||
test_value(ctx, &[TypedValue::I32(15), TypedValue::I32(3)]).unwrap(),
|
||||
ReturnValue::Value(TypedValue::I32(5)),
|
||||
);
|
||||
assert!(test_value(ctx, &[TypedValue::I32(15), TypedValue::I32(0)]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_unmarshall_then_body_then_marshall_unit() {
|
||||
fn test_unit(
|
||||
ctx: &mut u32,
|
||||
args: &[sandbox::TypedValue],
|
||||
) -> Result<ReturnValue, sandbox::HostError> {
|
||||
let mut args = args.iter();
|
||||
unmarshall_then_body_then_marshall!(
|
||||
args,
|
||||
ctx,
|
||||
(a: u32, b: u32) => {
|
||||
*ctx = a + b;
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let ctx = &mut 0;
|
||||
let result = test_unit(ctx, &[TypedValue::I32(2), TypedValue::I32(3)]).unwrap();
|
||||
assert_eq!(result, ReturnValue::Unit);
|
||||
assert_eq!(*ctx, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_define_func() {
|
||||
define_func!( <E: Ext> ext_gas (_ctx, amount: u32) => {
|
||||
let amount = <E::T as Trait>::Gas::from(amount);
|
||||
if !amount.is_zero() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(sandbox::HostError)
|
||||
}
|
||||
});
|
||||
let _f: fn(&mut Runtime<MockExt>, &[sandbox::TypedValue])
|
||||
-> Result<sandbox::ReturnValue, sandbox::HostError> = ext_gas::<MockExt>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_gen_signature() {
|
||||
assert_eq!(
|
||||
gen_signature!((i32)),
|
||||
FunctionType::new(vec![ValueType::I32], None),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
gen_signature!( (i32, u32) -> u32 ),
|
||||
FunctionType::new(vec![ValueType::I32, ValueType::I32], Some(ValueType::I32)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_unmarshall_then_body() {
|
||||
let args = vec![TypedValue::I32(5), TypedValue::I32(3)];
|
||||
let mut args = args.iter();
|
||||
|
||||
let ctx: &mut u32 = &mut 0;
|
||||
|
||||
let r = unmarshall_then_body!(
|
||||
{
|
||||
*ctx = a + b;
|
||||
a * b
|
||||
},
|
||||
ctx,
|
||||
args,
|
||||
a: u32,
|
||||
b: u32
|
||||
);
|
||||
|
||||
assert_eq!(*ctx, 8);
|
||||
assert_eq!(r, 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_define_env() {
|
||||
use crate::wasm::env_def::ImportSatisfyCheck;
|
||||
|
||||
define_env!(Env, <E: Ext>,
|
||||
ext_gas( _ctx, amount: u32 ) => {
|
||||
let amount = <E::T as Trait>::Gas::from(amount);
|
||||
if !amount.is_zero() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(sandbox::HostError)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert!(Env::can_satisfy(b"ext_gas", &FunctionType::new(vec![ValueType::I32], None)));
|
||||
assert!(!Env::can_satisfy(b"not_exists", &FunctionType::new(vec![], None)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::Runtime;
|
||||
use crate::exec::Ext;
|
||||
|
||||
use sandbox::{self, TypedValue};
|
||||
use parity_wasm::elements::{FunctionType, ValueType};
|
||||
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
|
||||
pub trait ConvertibleToWasm: Sized {
|
||||
const VALUE_TYPE: ValueType;
|
||||
type NativeType;
|
||||
fn to_typed_value(self) -> TypedValue;
|
||||
fn from_typed_value(_: TypedValue) -> Option<Self>;
|
||||
}
|
||||
impl ConvertibleToWasm for i32 {
|
||||
type NativeType = i32;
|
||||
const VALUE_TYPE: ValueType = ValueType::I32;
|
||||
fn to_typed_value(self) -> TypedValue {
|
||||
TypedValue::I32(self)
|
||||
}
|
||||
fn from_typed_value(v: TypedValue) -> Option<Self> {
|
||||
v.as_i32()
|
||||
}
|
||||
}
|
||||
impl ConvertibleToWasm for u32 {
|
||||
type NativeType = u32;
|
||||
const VALUE_TYPE: ValueType = ValueType::I32;
|
||||
fn to_typed_value(self) -> TypedValue {
|
||||
TypedValue::I32(self as i32)
|
||||
}
|
||||
fn from_typed_value(v: TypedValue) -> Option<Self> {
|
||||
match v {
|
||||
TypedValue::I32(v) => Some(v as u32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ConvertibleToWasm for u64 {
|
||||
type NativeType = u64;
|
||||
const VALUE_TYPE: ValueType = ValueType::I64;
|
||||
fn to_typed_value(self) -> TypedValue {
|
||||
TypedValue::I64(self as i64)
|
||||
}
|
||||
fn from_typed_value(v: TypedValue) -> Option<Self> {
|
||||
match v {
|
||||
TypedValue::I64(v) => Some(v as u64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type HostFunc<E> =
|
||||
fn(
|
||||
&mut Runtime<E>,
|
||||
&[sandbox::TypedValue]
|
||||
) -> Result<sandbox::ReturnValue, sandbox::HostError>;
|
||||
|
||||
pub(crate) trait FunctionImplProvider<E: Ext> {
|
||||
fn impls<F: FnMut(&[u8], HostFunc<E>)>(f: &mut F);
|
||||
}
|
||||
|
||||
/// This trait can be used to check whether the host environment can satisfy
|
||||
/// a requested function import.
|
||||
pub trait ImportSatisfyCheck {
|
||||
/// Returns `true` if the host environment contains a function with
|
||||
/// the specified name and its type matches to the given type, or `false`
|
||||
/// otherwise.
|
||||
fn can_satisfy(name: &[u8], func_type: &FunctionType) -> bool;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,707 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This module takes care of loading, checking and preprocessing of a
|
||||
//! wasm module before execution. It also extracts some essential information
|
||||
//! from a module.
|
||||
|
||||
use crate::wasm::env_def::ImportSatisfyCheck;
|
||||
use crate::wasm::PrefabWasmModule;
|
||||
use crate::{Schedule, Trait};
|
||||
|
||||
use parity_wasm::elements::{self, Internal, External, MemoryType, Type};
|
||||
use pwasm_utils;
|
||||
use pwasm_utils::rules;
|
||||
use rstd::prelude::*;
|
||||
use runtime_primitives::traits::{UniqueSaturatedInto, SaturatedConversion};
|
||||
|
||||
struct ContractModule<'a, Gas: 'a> {
|
||||
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
|
||||
///
|
||||
/// An `Option` is used here for loaning (`take()`-ing) the module.
|
||||
/// Invariant: Can't be `None` (i.e. on enter and on exit from the function
|
||||
/// the value *must* be `Some`).
|
||||
module: Option<elements::Module>,
|
||||
schedule: &'a Schedule<Gas>,
|
||||
}
|
||||
|
||||
impl<'a, Gas: 'a + From<u32> + UniqueSaturatedInto<u32> + Clone> ContractModule<'a, Gas> {
|
||||
/// Creates a new instance of `ContractModule`.
|
||||
///
|
||||
/// Returns `Err` if the `original_code` couldn't be decoded or
|
||||
/// if it contains an invalid module.
|
||||
fn new(
|
||||
original_code: &[u8],
|
||||
schedule: &'a Schedule<Gas>,
|
||||
) -> Result<ContractModule<'a, Gas>, &'static str> {
|
||||
use wasmi_validation::{validate_module, PlainValidator};
|
||||
|
||||
let module =
|
||||
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
|
||||
|
||||
// Make sure that the module is valid.
|
||||
validate_module::<PlainValidator>(&module).map_err(|_| "Module is not valid")?;
|
||||
|
||||
// Return a `ContractModule` instance with
|
||||
// __valid__ module.
|
||||
Ok(ContractModule {
|
||||
module: Some(module),
|
||||
schedule,
|
||||
})
|
||||
}
|
||||
|
||||
/// Ensures that module doesn't declare internal memories.
|
||||
///
|
||||
/// In this runtime we only allow wasm module to import memory from the environment.
|
||||
/// Memory section contains declarations of internal linear memories, so if we find one
|
||||
/// we reject such a module.
|
||||
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
|
||||
let module = self
|
||||
.module
|
||||
.as_ref()
|
||||
.expect("On entry to the function `module` can't be None; qed");
|
||||
if module
|
||||
.memory_section()
|
||||
.map_or(false, |ms| ms.entries().len() > 0)
|
||||
{
|
||||
return Err("module declares internal memory");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(&mut self) -> Result<(), &'static str> {
|
||||
let gas_rules =
|
||||
rules::Set::new(
|
||||
self.schedule.regular_op_cost.clone().saturated_into(),
|
||||
Default::default(),
|
||||
)
|
||||
.with_grow_cost(self.schedule.grow_mem_cost.clone().saturated_into())
|
||||
.with_forbidden_floats();
|
||||
|
||||
let module = self
|
||||
.module
|
||||
.take()
|
||||
.expect("On entry to the function `module` can't be `None`; qed");
|
||||
|
||||
let contract_module = pwasm_utils::inject_gas_counter(module, &gas_rules)
|
||||
.map_err(|_| "gas instrumentation failed")?;
|
||||
|
||||
self.module = Some(contract_module);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_stack_height_metering(&mut self) -> Result<(), &'static str> {
|
||||
let module = self
|
||||
.module
|
||||
.take()
|
||||
.expect("On entry to the function `module` can't be `None`; qed");
|
||||
|
||||
let contract_module =
|
||||
pwasm_utils::stack_height::inject_limiter(module, self.schedule.max_stack_height)
|
||||
.map_err(|_| "stack height instrumentation failed")?;
|
||||
|
||||
self.module = Some(contract_module);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that the module has required exported functions. For now
|
||||
/// these are just entrypoints:
|
||||
///
|
||||
/// - 'call'
|
||||
/// - 'deploy'
|
||||
///
|
||||
/// Any other exports are not allowed.
|
||||
fn scan_exports(&self) -> Result<(), &'static str> {
|
||||
let mut deploy_found = false;
|
||||
let mut call_found = false;
|
||||
|
||||
let module = self
|
||||
.module
|
||||
.as_ref()
|
||||
.expect("On entry to the function `module` can't be `None`; qed");
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let export_entries = module
|
||||
.export_section()
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[]);
|
||||
let func_entries = module
|
||||
.function_section()
|
||||
.map(|fs| fs.entries())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
// Function index space consists of imported function following by
|
||||
// declared functions. Calculate the total number of imported functions so
|
||||
// we can use it to convert indexes from function space to declared function space.
|
||||
let fn_space_offset = module
|
||||
.import_section()
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
match *entry.external() {
|
||||
External::Function(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.count();
|
||||
|
||||
for export in export_entries {
|
||||
match export.field() {
|
||||
"call" => call_found = true,
|
||||
"deploy" => deploy_found = true,
|
||||
_ => return Err("unknown export: expecting only deploy and call functions"),
|
||||
}
|
||||
|
||||
// Then check the export kind. "call" and "deploy" are
|
||||
// functions.
|
||||
let fn_idx = match export.internal() {
|
||||
Internal::Function(ref fn_idx) => *fn_idx,
|
||||
_ => return Err("expected a function"),
|
||||
};
|
||||
|
||||
// convert index from function index space to declared index space.
|
||||
let fn_idx = match fn_idx.checked_sub(fn_space_offset as u32) {
|
||||
Some(fn_idx) => fn_idx,
|
||||
None => {
|
||||
// Underflow here means fn_idx points to imported function which we don't allow!
|
||||
return Err("entry point points to an imported function");
|
||||
}
|
||||
};
|
||||
|
||||
// Then check the signature.
|
||||
// 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();
|
||||
let Type::Function(ref func_ty) = types
|
||||
.get(func_ty_idx as usize)
|
||||
.ok_or_else(|| "function has a non-existent type")?;
|
||||
if !(func_ty.params().is_empty() && func_ty.return_type().is_none()) {
|
||||
return Err("entry point has wrong signature");
|
||||
}
|
||||
}
|
||||
|
||||
if !deploy_found {
|
||||
return Err("deploy function isn't exported");
|
||||
}
|
||||
if !call_found {
|
||||
return Err("call function isn't exported");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Scan an import section if any.
|
||||
///
|
||||
/// This accomplishes two tasks:
|
||||
///
|
||||
/// - checks any imported function against defined host functions set, incl.
|
||||
/// their signatures.
|
||||
/// - if there is a memory import, returns it's descriptor
|
||||
fn scan_imports<C: ImportSatisfyCheck>(&self) -> Result<Option<&MemoryType>, &'static str> {
|
||||
let module = self
|
||||
.module
|
||||
.as_ref()
|
||||
.expect("On entry to the function `module` can't be `None`; qed");
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let import_entries = module
|
||||
.import_section()
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
let mut imported_mem_type = None;
|
||||
|
||||
for import in import_entries {
|
||||
if import.module() != "env" {
|
||||
// This import tries to import something from non-"env" module,
|
||||
// but all imports are located in "env" at the moment.
|
||||
return Err("module has imports from a non-'env' namespace");
|
||||
}
|
||||
|
||||
let type_idx = match import.external() {
|
||||
&External::Table(_) => return Err("Cannot import tables"),
|
||||
&External::Global(_) => return Err("Cannot import globals"),
|
||||
&External::Function(ref type_idx) => type_idx,
|
||||
&External::Memory(ref memory_type) => {
|
||||
if import.field() != "memory" {
|
||||
return Err("Memory import must have the field name 'memory'")
|
||||
}
|
||||
if imported_mem_type.is_some() {
|
||||
return Err("Multiple memory imports defined")
|
||||
}
|
||||
imported_mem_type = Some(memory_type);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let Type::Function(ref func_ty) = types
|
||||
.get(*type_idx as usize)
|
||||
.ok_or_else(|| "validation: import entry points to a non-existent type")?;
|
||||
|
||||
// We disallow importing `ext_println` unless debug features are enabled,
|
||||
// which should only be allowed on a dev chain
|
||||
if !self.schedule.enable_println && import.field().as_bytes() == b"ext_println" {
|
||||
return Err("module imports `ext_println` but debug features disabled");
|
||||
}
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
if import.field().as_bytes() == b"gas"
|
||||
|| !C::can_satisfy(import.field().as_bytes(), func_ty)
|
||||
{
|
||||
return Err("module imports a non-existent function");
|
||||
}
|
||||
}
|
||||
Ok(imported_mem_type)
|
||||
}
|
||||
|
||||
fn into_wasm_code(mut self) -> Result<Vec<u8>, &'static str> {
|
||||
elements::serialize(
|
||||
self.module
|
||||
.take()
|
||||
.expect("On entry to the function `module` can't be `None`; qed"),
|
||||
)
|
||||
.map_err(|_| "error serializing instrumented module")
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the given module given in `original_code`, performs some checks on it and
|
||||
/// does some preprocessing.
|
||||
///
|
||||
/// The checks are:
|
||||
///
|
||||
/// - provided code is a valid wasm module.
|
||||
/// - the module doesn't define an internal memory instance,
|
||||
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`,
|
||||
/// - all imported functions from the external environment matches defined by `env` module,
|
||||
///
|
||||
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
|
||||
pub fn prepare_contract<T: Trait, C: ImportSatisfyCheck>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T::Gas>,
|
||||
) -> Result<PrefabWasmModule, &'static str> {
|
||||
let mut contract_module = ContractModule::new(original_code, schedule)?;
|
||||
contract_module.scan_exports()?;
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
|
||||
struct MemoryDefinition {
|
||||
initial: u32,
|
||||
maximum: u32,
|
||||
}
|
||||
|
||||
let memory_def = if let Some(memory_type) = contract_module.scan_imports::<C>()? {
|
||||
// Inspect the module to extract the initial and maximum page count.
|
||||
let limits = memory_type.limits();
|
||||
match (limits.initial(), limits.maximum()) {
|
||||
(initial, Some(maximum)) if initial > maximum => {
|
||||
return Err(
|
||||
"Requested initial number of pages should not exceed the requested maximum",
|
||||
);
|
||||
}
|
||||
(_, Some(maximum)) if maximum > schedule.max_memory_pages => {
|
||||
return Err("Maximum number of pages should not exceed the configured maximum.");
|
||||
}
|
||||
(initial, Some(maximum)) => MemoryDefinition { initial, maximum },
|
||||
(_, None) => {
|
||||
// Maximum number of pages should be always declared.
|
||||
// This isn't a hard requirement and can be treated as a maximum set
|
||||
// to configured maximum.
|
||||
return Err("Maximum number of pages should be always declared.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If none memory imported then just crate an empty placeholder.
|
||||
// Any access to it will lead to out of bounds trap.
|
||||
MemoryDefinition {
|
||||
initial: 0,
|
||||
maximum: 0,
|
||||
}
|
||||
};
|
||||
|
||||
contract_module.inject_gas_metering()?;
|
||||
contract_module.inject_stack_height_metering()?;
|
||||
|
||||
Ok(PrefabWasmModule {
|
||||
schedule_version: schedule.version,
|
||||
initial: memory_def.initial,
|
||||
maximum: memory_def.maximum,
|
||||
_reserved: None,
|
||||
code: contract_module.into_wasm_code()?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::Test;
|
||||
use crate::exec::Ext;
|
||||
use std::fmt;
|
||||
use wabt;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
impl fmt::Debug for PrefabWasmModule {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "PreparedContract {{ .. }}")
|
||||
}
|
||||
}
|
||||
|
||||
// Define test environment for tests. We need ImportSatisfyCheck
|
||||
// implementation from it. So actual implementations doesn't matter.
|
||||
define_env!(TestEnv, <E: Ext>,
|
||||
panic(_ctx) => { unreachable!(); },
|
||||
|
||||
// gas is an implementation defined function and a contract can't import it.
|
||||
gas(_ctx, _amount: u32) => { unreachable!(); },
|
||||
|
||||
nop(_ctx, _unused: u64) => { unreachable!(); },
|
||||
|
||||
ext_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); },
|
||||
);
|
||||
|
||||
macro_rules! prepare_test {
|
||||
($name:ident, $wat:expr, $($expected:tt)*) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let wasm = wabt::Wat2Wasm::new().validate(false).convert($wat).unwrap();
|
||||
let schedule = Schedule::<u64>::default();
|
||||
let r = prepare_contract::<Test, TestEnv>(wasm.as_ref(), &schedule);
|
||||
assert_matches!(r, $($expected)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
prepare_test!(no_floats,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
(drop
|
||||
(f32.add
|
||||
(f32.const 0)
|
||||
(f32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(func (export "deploy"))
|
||||
)"#,
|
||||
Err("gas instrumentation failed")
|
||||
);
|
||||
|
||||
mod memories {
|
||||
use super::*;
|
||||
|
||||
// Tests below assumes that maximum page number is configured to a certain number.
|
||||
#[test]
|
||||
fn assume_memory_size() {
|
||||
assert_eq!(Schedule::<u64>::default().max_memory_pages, 16);
|
||||
}
|
||||
|
||||
prepare_test!(memory_with_one_page,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(internal_memory_declaration,
|
||||
r#"
|
||||
(module
|
||||
(memory 1 1)
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module declares internal memory")
|
||||
);
|
||||
|
||||
prepare_test!(no_memory_import,
|
||||
r#"
|
||||
(module
|
||||
;; no memory imported
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(initial_exceeds_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 16 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Module is not valid")
|
||||
);
|
||||
|
||||
prepare_test!(no_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Maximum number of pages should be always declared.")
|
||||
);
|
||||
|
||||
prepare_test!(requested_maximum_exceeds_configured_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 17))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Maximum number of pages should not exceed the configured maximum.")
|
||||
);
|
||||
|
||||
prepare_test!(field_name_not_memory,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "forgetit" (memory 1 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Memory import must have the field name 'memory'")
|
||||
);
|
||||
|
||||
prepare_test!(multiple_memory_imports,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Module is not valid")
|
||||
);
|
||||
|
||||
prepare_test!(table_import,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "table" (table 1 anyfunc))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Cannot import tables")
|
||||
);
|
||||
|
||||
prepare_test!(global_import,
|
||||
r#"
|
||||
(module
|
||||
(global $g (import "env" "global") i32)
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Cannot import globals")
|
||||
);
|
||||
}
|
||||
|
||||
mod imports {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(can_import_legit_function,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "nop" (func (param i64)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
// even though gas is defined the contract can't import it since
|
||||
// it is an implementation defined.
|
||||
prepare_test!(can_not_import_gas_function,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "gas" (func (param i32)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
);
|
||||
|
||||
// nothing can be imported from non-"env" module for now.
|
||||
prepare_test!(non_env_import,
|
||||
r#"
|
||||
(module
|
||||
(import "another_module" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module has imports from a non-'env' namespace")
|
||||
);
|
||||
|
||||
// wrong signature
|
||||
prepare_test!(wrong_signature,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "gas" (func (param i64)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
);
|
||||
|
||||
prepare_test!(unknown_func_name,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "unknown_func" (func))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
);
|
||||
|
||||
prepare_test!(ext_println_debug_disabled,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "ext_println" (func $ext_println (param i32 i32)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports `ext_println` but debug features disabled")
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ext_println_debug_enabled() {
|
||||
let wasm = wabt::Wat2Wasm::new().validate(false).convert(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "ext_println" (func $ext_println (param i32 i32)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#
|
||||
).unwrap();
|
||||
let mut schedule = Schedule::<u64>::default();
|
||||
schedule.enable_println = true;
|
||||
let r = prepare_contract::<Test, TestEnv>(wasm.as_ref(), &schedule);
|
||||
assert_matches!(r, Ok(_));
|
||||
}
|
||||
}
|
||||
|
||||
mod entrypoints {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(it_works,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(omit_deploy,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
)
|
||||
"#,
|
||||
Err("deploy function isn't exported")
|
||||
);
|
||||
|
||||
prepare_test!(omit_call,
|
||||
r#"
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("call function isn't exported")
|
||||
);
|
||||
|
||||
// Try to use imported function as an entry point.
|
||||
prepare_test!(try_sneak_export_as_entrypoint,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "panic" (func))
|
||||
|
||||
(func (export "deploy"))
|
||||
|
||||
(export "call" (func 0))
|
||||
)
|
||||
"#,
|
||||
Err("entry point points to an imported function")
|
||||
);
|
||||
|
||||
// Try to use imported function as an entry point.
|
||||
prepare_test!(try_sneak_export_as_global,
|
||||
r#"
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
(global (export "call") i32 (i32.const 0))
|
||||
)
|
||||
"#,
|
||||
Err("expected a function")
|
||||
);
|
||||
|
||||
prepare_test!(wrong_signature,
|
||||
r#"
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
(func (export "call") (param i32))
|
||||
)
|
||||
"#,
|
||||
Err("entry point has wrong signature")
|
||||
);
|
||||
|
||||
prepare_test!(unknown_exports,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
(func (export "whatevs"))
|
||||
)
|
||||
"#,
|
||||
Err("unknown export: expecting only deploy and call functions")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Environment definition of the wasm smart-contract runtime.
|
||||
|
||||
use crate::{Schedule, Trait, CodeHash, ComputeDispatchFee, BalanceOf};
|
||||
use crate::exec::{
|
||||
Ext, VmExecResult, OutputBuf, EmptyOutputBuf, CallReceipt, InstantiateReceipt, StorageKey,
|
||||
TopicOf,
|
||||
};
|
||||
use crate::gas::{GasMeter, Token, GasMeterResult, approx_gas_for_balance};
|
||||
use sandbox;
|
||||
use system;
|
||||
use rstd::prelude::*;
|
||||
use rstd::mem;
|
||||
use parity_codec::{Decode, Encode};
|
||||
use runtime_primitives::traits::{CheckedMul, CheckedAdd, Bounded, SaturatedConversion};
|
||||
|
||||
/// Enumerates all possible *special* trap conditions.
|
||||
///
|
||||
/// In this runtime traps used not only for signaling about errors but also
|
||||
/// to just terminate quickly in some cases.
|
||||
enum SpecialTrap {
|
||||
/// Signals that trap was generated in response to call `ext_return` host function.
|
||||
Return(OutputBuf),
|
||||
}
|
||||
|
||||
/// Can only be used for one call.
|
||||
pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> {
|
||||
ext: &'a mut E,
|
||||
input_data: &'data [u8],
|
||||
// A VM can return a result only once and only by value. So
|
||||
// we wrap output buffer to make it possible to take the buffer out.
|
||||
empty_output_buf: Option<EmptyOutputBuf>,
|
||||
scratch_buf: Vec<u8>,
|
||||
schedule: &'a Schedule<<E::T as Trait>::Gas>,
|
||||
memory: sandbox::Memory,
|
||||
gas_meter: &'a mut GasMeter<E::T>,
|
||||
special_trap: Option<SpecialTrap>,
|
||||
}
|
||||
impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> {
|
||||
pub(crate) fn new(
|
||||
ext: &'a mut E,
|
||||
input_data: &'data [u8],
|
||||
empty_output_buf: EmptyOutputBuf,
|
||||
schedule: &'a Schedule<<E::T as Trait>::Gas>,
|
||||
memory: sandbox::Memory,
|
||||
gas_meter: &'a mut GasMeter<E::T>,
|
||||
) -> Self {
|
||||
Runtime {
|
||||
ext,
|
||||
input_data,
|
||||
empty_output_buf: Some(empty_output_buf),
|
||||
scratch_buf: Vec::new(),
|
||||
schedule,
|
||||
memory,
|
||||
gas_meter,
|
||||
special_trap: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn memory(&self) -> &sandbox::Memory {
|
||||
&self.memory
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_execution_result<E: Ext>(
|
||||
runtime: Runtime<E>,
|
||||
sandbox_err: Option<sandbox::Error>,
|
||||
) -> VmExecResult {
|
||||
// Check the exact type of the error. It could be plain trap or
|
||||
// special runtime trap the we must recognize.
|
||||
match (sandbox_err, runtime.special_trap) {
|
||||
// No traps were generated. Proceed normally.
|
||||
(None, None) => VmExecResult::Ok,
|
||||
// Special case. The trap was the result of the execution `return` host function.
|
||||
(Some(sandbox::Error::Execution), Some(SpecialTrap::Return(buf))) => VmExecResult::Returned(buf),
|
||||
// Any other kind of a trap should result in a failure.
|
||||
(Some(_), _) => VmExecResult::Trap("during execution"),
|
||||
// Any other case (such as special trap flag without actual trap) signifies
|
||||
// a logic error.
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum RuntimeToken<Gas> {
|
||||
/// Explicit call to the `gas` function. Charge the gas meter
|
||||
/// with the value provided.
|
||||
Explicit(u32),
|
||||
/// The given number of bytes is read from the sandbox memory.
|
||||
ReadMemory(u32),
|
||||
/// The given number of bytes is written to the sandbox memory.
|
||||
WriteMemory(u32),
|
||||
/// The given number of bytes is read from the sandbox memory and
|
||||
/// is returned as the return data buffer of the call.
|
||||
ReturnData(u32),
|
||||
/// Dispatch fee calculated by `T::ComputeDispatchFee`.
|
||||
ComputedDispatchFee(Gas),
|
||||
/// (topic_count, data_bytes): A buffer of the given size is posted as an event indexed with the
|
||||
/// given number of topics.
|
||||
DepositEvent(u32, u32),
|
||||
}
|
||||
|
||||
impl<T: Trait> Token<T> for RuntimeToken<T::Gas> {
|
||||
type Metadata = Schedule<T::Gas>;
|
||||
|
||||
fn calculate_amount(&self, metadata: &Schedule<T::Gas>) -> T::Gas {
|
||||
use self::RuntimeToken::*;
|
||||
let value = match *self {
|
||||
Explicit(amount) => Some(amount.into()),
|
||||
ReadMemory(byte_count) => metadata
|
||||
.sandbox_data_read_cost
|
||||
.checked_mul(&byte_count.into()),
|
||||
WriteMemory(byte_count) => metadata
|
||||
.sandbox_data_write_cost
|
||||
.checked_mul(&byte_count.into()),
|
||||
ReturnData(byte_count) => metadata
|
||||
.return_data_per_byte_cost
|
||||
.checked_mul(&byte_count.into()),
|
||||
DepositEvent(topic_count, data_byte_count) => {
|
||||
let data_cost = metadata
|
||||
.event_data_per_byte_cost
|
||||
.checked_mul(&data_byte_count.into());
|
||||
|
||||
let topics_cost = metadata
|
||||
.event_per_topic_cost
|
||||
.checked_mul(&topic_count.into());
|
||||
|
||||
data_cost
|
||||
.and_then(|data_cost| {
|
||||
topics_cost.and_then(|topics_cost| {
|
||||
data_cost.checked_add(&topics_cost)
|
||||
})
|
||||
})
|
||||
.and_then(|data_and_topics_cost|
|
||||
data_and_topics_cost.checked_add(&metadata.event_base_cost)
|
||||
)
|
||||
},
|
||||
ComputedDispatchFee(gas) => Some(gas),
|
||||
};
|
||||
|
||||
value.unwrap_or_else(|| Bounded::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge the gas meter with the specified token.
|
||||
///
|
||||
/// Returns `Err(HostError)` if there is not enough gas.
|
||||
fn charge_gas<T: Trait, Tok: Token<T>>(
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
metadata: &Tok::Metadata,
|
||||
token: Tok,
|
||||
) -> Result<(), sandbox::HostError> {
|
||||
match gas_meter.charge(metadata, token) {
|
||||
GasMeterResult::Proceed => Ok(()),
|
||||
GasMeterResult::OutOfGas => Err(sandbox::HostError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory, consuming an appropriate amount of
|
||||
/// gas.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory<E: Ext>(
|
||||
ctx: &mut Runtime<E>,
|
||||
ptr: u32,
|
||||
len: u32,
|
||||
) -> Result<Vec<u8>, sandbox::HostError> {
|
||||
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.resize(len as usize, 0);
|
||||
|
||||
ctx.memory().get(ptr, &mut buf)?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory into the supplied buffer, consuming
|
||||
/// an appropriate amount of gas.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory_into_buf<E: Ext>(
|
||||
ctx: &mut Runtime<E>,
|
||||
ptr: u32,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(), sandbox::HostError> {
|
||||
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(buf.len() as u32))?;
|
||||
|
||||
ctx.memory().get(ptr, buf).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Write the given buffer to the designated location in the sandbox memory, consuming
|
||||
/// an appropriate amount of gas.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - designated area is not within the bounds of the sandbox memory.
|
||||
fn write_sandbox_memory<T: Trait>(
|
||||
schedule: &Schedule<T::Gas>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
memory: &sandbox::Memory,
|
||||
ptr: u32,
|
||||
buf: &[u8],
|
||||
) -> Result<(), sandbox::HostError> {
|
||||
charge_gas(gas_meter, schedule, RuntimeToken::WriteMemory(buf.len() as u32))?;
|
||||
|
||||
memory.set(ptr, buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ***********************************************************
|
||||
// * AFTER MAKING A CHANGE MAKE SURE TO UPDATE COMPLEXITY.MD *
|
||||
// ***********************************************************
|
||||
|
||||
// Define a function `fn init_env<E: Ext>() -> HostFunctionSet<E>` that returns
|
||||
// a function set which can be imported by an executed contract.
|
||||
define_env!(Env, <E: Ext>,
|
||||
|
||||
// Account for used gas. Traps if gas used is greater than gas limit.
|
||||
//
|
||||
// NOTE: This is a implementation defined call and is NOT a part of the public API.
|
||||
// This call is supposed to be called only by instrumentation injected code.
|
||||
//
|
||||
// - amount: How much gas is used.
|
||||
gas(ctx, amount: u32) => {
|
||||
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::Explicit(amount))?;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Change the value at the given key in the storage or remove the entry.
|
||||
//
|
||||
// - key_ptr: pointer into the linear
|
||||
// memory where the location of the requested value is placed.
|
||||
// - value_non_null: if set to 0, then the entry
|
||||
// at the given location will be removed.
|
||||
// - value_ptr: pointer into the linear memory
|
||||
// where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored.
|
||||
// - value_len: the length of the value. If `value_non_null` is set to 0, then this parameter is ignored.
|
||||
ext_set_storage(ctx, key_ptr: u32, value_non_null: u32, value_ptr: u32, value_len: u32) => {
|
||||
let mut key: StorageKey = [0; 32];
|
||||
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
||||
let value =
|
||||
if value_non_null != 0 {
|
||||
Some(read_sandbox_memory(ctx, value_ptr, value_len)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ctx.ext.set_storage(key, value);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Retrieve the value at the given location from the strorage and return 0.
|
||||
// If there is no entry at the given location then this function will return 1 and
|
||||
// clear the scratch buffer.
|
||||
//
|
||||
// - key_ptr: pointer into the linear memory where the key
|
||||
// of the requested value is placed.
|
||||
ext_get_storage(ctx, key_ptr: u32) -> u32 => {
|
||||
let mut key: StorageKey = [0; 32];
|
||||
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
||||
if let Some(value) = ctx.ext.get_storage(&key) {
|
||||
ctx.scratch_buf = value;
|
||||
Ok(0)
|
||||
} else {
|
||||
ctx.scratch_buf.clear();
|
||||
Ok(1)
|
||||
}
|
||||
},
|
||||
|
||||
// Make a call to another contract.
|
||||
//
|
||||
// Returns 0 on the successful execution and puts the result data returned
|
||||
// by the callee into the scratch buffer. Otherwise, returns 1 and clears the scratch
|
||||
// buffer.
|
||||
//
|
||||
// - callee_ptr: a pointer to the address of the callee contract.
|
||||
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
||||
// - callee_len: length of the address buffer.
|
||||
// - gas: how much gas to devote to the execution.
|
||||
// - value_ptr: a pointer to the buffer with value, how much value to send.
|
||||
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||
// - value_len: length of the value buffer.
|
||||
// - input_data_ptr: a pointer to a buffer to be used as input data to the callee.
|
||||
// - input_data_len: length of the input data buffer.
|
||||
ext_call(
|
||||
ctx,
|
||||
callee_ptr: u32,
|
||||
callee_len: u32,
|
||||
gas: u64,
|
||||
value_ptr: u32,
|
||||
value_len: u32,
|
||||
input_data_ptr: u32,
|
||||
input_data_len: u32
|
||||
) -> u32 => {
|
||||
let callee = {
|
||||
let callee_buf = read_sandbox_memory(ctx, callee_ptr, callee_len)?;
|
||||
<<E as Ext>::T as system::Trait>::AccountId::decode(&mut &callee_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?
|
||||
};
|
||||
let value = {
|
||||
let value_buf = read_sandbox_memory(ctx, value_ptr, value_len)?;
|
||||
BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?
|
||||
};
|
||||
let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?;
|
||||
|
||||
// Grab the scratch buffer and put in its' place an empty one.
|
||||
// We will use it for creating `EmptyOutputBuf` container for the call.
|
||||
let scratch_buf = mem::replace(&mut ctx.scratch_buf, Vec::new());
|
||||
let empty_output_buf = EmptyOutputBuf::from_spare_vec(scratch_buf);
|
||||
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
} else {
|
||||
gas.saturated_into()
|
||||
};
|
||||
let ext = &mut ctx.ext;
|
||||
let call_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
|
||||
match nested_meter {
|
||||
Some(nested_meter) => {
|
||||
ext.call(
|
||||
&callee,
|
||||
value,
|
||||
nested_meter,
|
||||
&input_data,
|
||||
empty_output_buf
|
||||
)
|
||||
.map_err(|_| ())
|
||||
}
|
||||
// there is not enough gas to allocate for the nested call.
|
||||
None => Err(()),
|
||||
}
|
||||
});
|
||||
|
||||
match call_outcome {
|
||||
Ok(CallReceipt { output_data }) => {
|
||||
ctx.scratch_buf = output_data;
|
||||
Ok(0)
|
||||
},
|
||||
Err(_) => Ok(1),
|
||||
}
|
||||
},
|
||||
|
||||
// Instantiate a contract with code returned by the specified initializer code.
|
||||
//
|
||||
// This function creates an account and executes initializer code. After the execution,
|
||||
// the returned buffer is saved as the code of the created account.
|
||||
//
|
||||
// Returns 0 on the successful contract creation and puts the address
|
||||
// of the created contract into the scratch buffer.
|
||||
// Otherwise, returns 1 and clears the scratch buffer.
|
||||
//
|
||||
// - init_code_ptr: a pointer to the buffer that contains the initializer code.
|
||||
// - init_code_len: length of the initializer code buffer.
|
||||
// - gas: how much gas to devote to the execution of the initializer code.
|
||||
// - value_ptr: a pointer to the buffer with value, how much value to send.
|
||||
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||
// - value_len: length of the value buffer.
|
||||
// - input_data_ptr: a pointer to a buffer to be used as input data to the initializer code.
|
||||
// - input_data_len: length of the input data buffer.
|
||||
ext_create(
|
||||
ctx,
|
||||
init_code_ptr: u32,
|
||||
init_code_len: u32,
|
||||
gas: u64,
|
||||
value_ptr: u32,
|
||||
value_len: u32,
|
||||
input_data_ptr: u32,
|
||||
input_data_len: u32
|
||||
) -> u32 => {
|
||||
let code_hash = {
|
||||
let code_hash_buf = read_sandbox_memory(ctx, init_code_ptr, init_code_len)?;
|
||||
<CodeHash<<E as Ext>::T>>::decode(&mut &code_hash_buf[..]).ok_or_else(|| sandbox::HostError)?
|
||||
};
|
||||
let value = {
|
||||
let value_buf = read_sandbox_memory(ctx, value_ptr, value_len)?;
|
||||
BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?
|
||||
};
|
||||
let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?;
|
||||
|
||||
// Clear the scratch buffer in any case.
|
||||
ctx.scratch_buf.clear();
|
||||
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
} else {
|
||||
gas.saturated_into()
|
||||
};
|
||||
let ext = &mut ctx.ext;
|
||||
let instantiate_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
|
||||
match nested_meter {
|
||||
Some(nested_meter) => {
|
||||
ext.instantiate(
|
||||
&code_hash,
|
||||
value,
|
||||
nested_meter,
|
||||
&input_data
|
||||
)
|
||||
.map_err(|_| ())
|
||||
}
|
||||
// there is not enough gas to allocate for the nested call.
|
||||
None => Err(()),
|
||||
}
|
||||
});
|
||||
match instantiate_outcome {
|
||||
Ok(InstantiateReceipt { address }) => {
|
||||
// Write the address to the scratch buffer.
|
||||
address.encode_to(&mut ctx.scratch_buf);
|
||||
Ok(0)
|
||||
},
|
||||
Err(_) => Ok(1),
|
||||
}
|
||||
},
|
||||
|
||||
// Save a data buffer as a result of the execution, terminate the execution and return a
|
||||
// successful result to the caller.
|
||||
ext_return(ctx, data_ptr: u32, data_len: u32) => {
|
||||
match ctx
|
||||
.gas_meter
|
||||
.charge(
|
||||
ctx.schedule,
|
||||
RuntimeToken::ReturnData(data_len)
|
||||
)
|
||||
{
|
||||
GasMeterResult::Proceed => (),
|
||||
GasMeterResult::OutOfGas => return Err(sandbox::HostError),
|
||||
}
|
||||
|
||||
let empty_output_buf = ctx
|
||||
.empty_output_buf
|
||||
.take()
|
||||
.expect(
|
||||
"`empty_output_buf` is taken only here;
|
||||
`ext_return` traps;
|
||||
`Runtime` can only be used only for one execution;
|
||||
qed"
|
||||
);
|
||||
let output_buf = empty_output_buf.fill(
|
||||
data_len as usize,
|
||||
|slice_mut| {
|
||||
// Read the memory at the specified pointer to the provided slice.
|
||||
ctx.memory.get(data_ptr, slice_mut)
|
||||
}
|
||||
)?;
|
||||
ctx.special_trap = Some(SpecialTrap::Return(output_buf));
|
||||
|
||||
// The trap mechanism is used to immediately terminate the execution.
|
||||
// This trap should be handled appropriately before returning the result
|
||||
// to the user of this crate.
|
||||
Err(sandbox::HostError)
|
||||
},
|
||||
|
||||
// Stores the address of the caller into the scratch buffer.
|
||||
//
|
||||
// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the extrinsic
|
||||
// will be returned. Otherwise, if this call is initiated by another contract then the address
|
||||
// of the contract will be returned.
|
||||
ext_caller(ctx) => {
|
||||
ctx.scratch_buf = ctx.ext.caller().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the address of the current contract into the scratch buffer.
|
||||
ext_address(ctx) => {
|
||||
ctx.scratch_buf = ctx.ext.address().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the gas price for the current transaction into the scratch buffer.
|
||||
//
|
||||
// The data is encoded as T::Balance. The current contents of the scratch buffer are overwritten.
|
||||
ext_gas_price(ctx) => {
|
||||
ctx.scratch_buf = ctx.gas_meter.gas_price().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the amount of gas left into the scratch buffer.
|
||||
//
|
||||
// The data is encoded as T::Balance. The current contents of the scratch buffer are overwritten.
|
||||
ext_gas_left(ctx) => {
|
||||
ctx.scratch_buf = ctx.gas_meter.gas_left().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the balance of the current account into the scratch buffer.
|
||||
//
|
||||
// The data is encoded as T::Balance. The current contents of the scratch buffer are overwritten.
|
||||
ext_balance(ctx) => {
|
||||
ctx.scratch_buf = ctx.ext.balance().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the value transferred along with this call or as endowment into the scratch buffer.
|
||||
//
|
||||
// The data is encoded as T::Balance. The current contents of the scratch buffer are overwritten.
|
||||
ext_value_transferred(ctx) => {
|
||||
ctx.scratch_buf = ctx.ext.value_transferred().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the random number for the current block for the given subject into the scratch
|
||||
// buffer.
|
||||
//
|
||||
// The data is encoded as T::Hash. The current contents of the scratch buffer are
|
||||
// overwritten.
|
||||
ext_random(ctx, subject_ptr: u32, subject_len: u32) => {
|
||||
// The length of a subject can't exceed `max_subject_len`.
|
||||
if subject_len > ctx.schedule.max_subject_len {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
let subject_buf = read_sandbox_memory(ctx, subject_ptr, subject_len)?;
|
||||
ctx.scratch_buf = ctx.ext.random(&subject_buf).encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Load the latest block timestamp into the scratch buffer
|
||||
ext_now(ctx) => {
|
||||
ctx.scratch_buf = ctx.ext.now().encode();
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Decodes the given buffer as a `T::Call` and adds it to the list
|
||||
// of to-be-dispatched calls.
|
||||
//
|
||||
// All calls made it to the top-level context will be dispatched before
|
||||
// finishing the execution of the calling extrinsic.
|
||||
ext_dispatch_call(ctx, call_ptr: u32, call_len: u32) => {
|
||||
let call = {
|
||||
let call_buf = read_sandbox_memory(ctx, call_ptr, call_len)?;
|
||||
<<<E as Ext>::T as Trait>::Call>::decode(&mut &call_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?
|
||||
};
|
||||
|
||||
// Charge gas for dispatching this call.
|
||||
let fee = {
|
||||
let balance_fee = <<E as Ext>::T as Trait>::ComputeDispatchFee::compute_dispatch_fee(&call);
|
||||
approx_gas_for_balance::<<E as Ext>::T>(ctx.gas_meter.gas_price(), balance_fee)
|
||||
};
|
||||
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::ComputedDispatchFee(fee))?;
|
||||
|
||||
ctx.ext.note_dispatch_call(call);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Returns the size of the input buffer.
|
||||
ext_input_size(ctx) -> u32 => {
|
||||
Ok(ctx.input_data.len() as u32)
|
||||
},
|
||||
|
||||
// Copy data from the input buffer starting from `offset` with length `len` into the contract memory.
|
||||
// The region at which the data should be put is specified by `dest_ptr`.
|
||||
ext_input_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => {
|
||||
let offset = offset as usize;
|
||||
if offset > ctx.input_data.len() {
|
||||
// Offset can't be larger than input buffer length.
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
// This can't panic since `offset <= ctx.input_data.len()`.
|
||||
let src = &ctx.input_data[offset..];
|
||||
if src.len() != len as usize {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
// Finally, perform the write.
|
||||
write_sandbox_memory(
|
||||
ctx.schedule,
|
||||
ctx.gas_meter,
|
||||
&ctx.memory,
|
||||
dest_ptr,
|
||||
src,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Returns the size of the scratch buffer.
|
||||
ext_scratch_size(ctx) -> u32 => {
|
||||
Ok(ctx.scratch_buf.len() as u32)
|
||||
},
|
||||
|
||||
// Copy data from the scratch buffer starting from `offset` with length `len` into the contract memory.
|
||||
// The region at which the data should be put is specified by `dest_ptr`.
|
||||
ext_scratch_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => {
|
||||
let offset = offset as usize;
|
||||
if offset > ctx.scratch_buf.len() {
|
||||
// Offset can't be larger than scratch buffer length.
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
// This can't panic since `offset <= ctx.scratch_buf.len()`.
|
||||
let src = &ctx.scratch_buf[offset..];
|
||||
if src.len() != len as usize {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
// Finally, perform the write.
|
||||
write_sandbox_memory(
|
||||
ctx.schedule,
|
||||
ctx.gas_meter,
|
||||
&ctx.memory,
|
||||
dest_ptr,
|
||||
src,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Deposit a contract event with the data buffer and optional list of topics. There is a limit
|
||||
// on the maximum number of topics specified by `max_event_topics`.
|
||||
//
|
||||
// - topics_ptr - a pointer to the buffer of topics encoded as `Vec<T::Hash>`. The value of this
|
||||
// is ignored if `topics_len` is set to 0. The topics list can't contain duplicates.
|
||||
// - topics_len - the length of the topics buffer. Pass 0 if you want to pass an empty vector.
|
||||
// - data_ptr - a pointer to a raw data buffer which will saved along the event.
|
||||
// - data_len - the length of the data buffer.
|
||||
ext_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => {
|
||||
let mut topics = match topics_len {
|
||||
0 => Vec::new(),
|
||||
_ => {
|
||||
let topics_buf = read_sandbox_memory(ctx, topics_ptr, topics_len)?;
|
||||
Vec::<TopicOf<<E as Ext>::T>>::decode(&mut &topics_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?
|
||||
}
|
||||
};
|
||||
|
||||
// If there are more than `max_event_topics`, then trap.
|
||||
if topics.len() > ctx.schedule.max_event_topics as usize {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
// Check for duplicate topics. If there are any, then trap.
|
||||
if has_duplicates(&mut topics) {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
|
||||
let event_data = read_sandbox_memory(ctx, data_ptr, data_len)?;
|
||||
|
||||
match ctx
|
||||
.gas_meter
|
||||
.charge(
|
||||
ctx.schedule,
|
||||
RuntimeToken::DepositEvent(topics.len() as u32, data_len)
|
||||
)
|
||||
{
|
||||
GasMeterResult::Proceed => (),
|
||||
GasMeterResult::OutOfGas => return Err(sandbox::HostError),
|
||||
}
|
||||
ctx.ext.deposit_event(topics, event_data);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Set rent allowance of the contract
|
||||
//
|
||||
// - value_ptr: a pointer to the buffer with value, how much to allow for rent
|
||||
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||
// - value_len: length of the value buffer.
|
||||
ext_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
|
||||
let value = {
|
||||
let value_buf = read_sandbox_memory(ctx, value_ptr, value_len)?;
|
||||
BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..])
|
||||
.ok_or_else(|| sandbox::HostError)?
|
||||
};
|
||||
ctx.ext.set_rent_allowance(value);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Stores the rent allowance into the scratch buffer.
|
||||
//
|
||||
// The data is encoded as T::Balance. The current contents of the scratch buffer are overwritten.
|
||||
ext_rent_allowance(ctx) => {
|
||||
ctx.scratch_buf = ctx.ext.rent_allowance().encode();
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Prints utf8 encoded string from the data buffer.
|
||||
// Only available on `--dev` chains.
|
||||
// This function may be removed at any time, superseded by a more general contract debugging feature.
|
||||
ext_println(ctx, str_ptr: u32, str_len: u32) => {
|
||||
let data = read_sandbox_memory(ctx, str_ptr, str_len)?;
|
||||
if let Ok(utf8) = core::str::from_utf8(&data) {
|
||||
runtime_io::print(utf8);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
/// Finds duplicates in a given vector.
|
||||
///
|
||||
/// This function has complexity of O(n log n) and no additional memory is required, although
|
||||
/// the order of items is not preserved.
|
||||
fn has_duplicates<T: PartialEq + AsRef<[u8]>>(items: &mut Vec<T>) -> bool {
|
||||
// Sort the vector
|
||||
items.sort_unstable_by(|a, b| {
|
||||
Ord::cmp(a.as_ref(), b.as_ref())
|
||||
});
|
||||
// And then find any two consecutive equal elements.
|
||||
items.windows(2).any(|w| {
|
||||
match w {
|
||||
&[ref a, ref b] => a == b,
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user