Github: https://github.com/patractlabs/europa
Europa is a sandbox environment that runs FRAME Contracts pallet
, and it is also a framework that provides a sandbox environment for Substrate Runtime. Therefore, developers can use the executable files compiled by Europa to debug the ink!
contract, and they can also use the library provided by Europa as a framework to implement a sandbox environment for the Runtime in their Substrate project.
Europa provides the minimal service components that support Runtime to package and execute transactions normally. Therefore, although Europa can be regarded as a chain node, this node has no consensus system, no network connection, and only retains the Runtime executor, RPC and some other necessary services. In addition, as a WASM contract running sandbox, we modified FRAME Contracts pallet
to provide more detailed debugging information, including contract execution process information (in FRAME Contracts
layer) and WASM execution information (in WASMI execution layer).
In order to facilitate debugging and add new features, Europa removed all WASM components in Substrate, so any std
code can be added to the entire system of Europa to meet experimental requirements.
v0.1: Have an independent runtime environment to facilitate more subsequent expansion directions.
v0.2: Modify at FRAME Contracts pallet
level to provide more information。
v0.3: Improve the development experience, strengthen collaboration with other tools, and extend the sandbox to be compatible with other runtime modules。
We submitted a proposal for Europa v0.1 two months ago, and wrote the treasury report. In v0.1, Europa has completed the basic framework of the Runtime sandbox, removed unnecessary modules related to the chain, removed the functional components of WASM, and provided many necessary tool components as a sandbox.
FRAME Contracts Pallet
to provide more debug informationAlthough FRAME Contracts pallet
has been iterative, its error messages are not friendly to contract development. Although in Substrate's Commit #6773 and Commit #7017, the error handling of contracts has been improved a lot, but these error messages are mainly concentrated in the FRAME Contracts pallet
layer, missing a lot of error information about WASM execution layer. For example, WASM execution crash errors or out-of-bounds errors do not have detailed information. For example, this part of the code in Substrate [frame/contracts/src/wasm/runtime.rs:LL367
](https://github.com/paritytech/ substrate/blob/master/frame/contracts/src/wasm/runtime.rs#L367)
// Any other kind of a trap should result in a failure.
Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) =>
Err(Error::<E::T>::ContractTrapped)?
}
}
As shown above, because sp_sandbox::Error
only provides three types of errors, and these error types do not contain more detailed reasons for this error. For example, developers only know that it is an execution error of Execution
and an out of bounds error of OutOfBounds
, And all have been converted into ContractTrapped
. Therefore, the reason for the internal crash of the contract cannot be known to the outside world. In our fork development of FRAME Contracts pallet
, we will supplement and perfect this short board.
In the execution environment of the contract wasmi
, all error messages are discarded. For example, in this part of the code in Substrate primitives/sandbox/with_std.rs:L287
, The information of _err
of Err(_err)
is discarded.
match result {
Ok(None) => Ok(ReturnValue::Unit),
Ok(Some(val)) => Ok(ReturnValue::Value(val.into())),
Err(_err) => Err(Error::Execution),
}
Although the debug_message
field is provided in the contract RPC call contracts_call
to provide the function of returning more information before the formal execution in the future, more detailed information still needs some customized development to provide. For details, please refer to the discussion in ink! issue #589. Therefore, the developers cannot well know the error information in the current FRAME Contracts
layer and the wasmi
interpretor during the execution of the contract.
On the other hand, when a contract calls a contract, the current FRAME Contracts pallet
cannot tell the developer which layer of the contracts has a problem. In other words, there is no Contract Stack trace
information about the execution process of the contract in the FRAME Contracts pallet
.
In addition, as a tool for contract debugging, the contract module lacks a lot of display for internal execution information, such as displaying the value of the selector
called by the current contract and the parameters passed by the contract. On the other hand, we can count the gas consumption of the contract calling the contract (not the gas consumption of a single contract, but the gas consumption during the execution), and the number of times of reading, writing, storing, and accessing other interfaces.
Developers need to know some variables or information during the execution of the contract. The easiest way is to print logs during the contract execution.
For printing logs information, currently seal_println(_ctx, _ptr: u32, _len: u32)
is provided in FRAME Contracts pallet
, and ::ink_env::debug_println()
is provided in ink!
. This group of functions plus ink_prelude::format!
can be combined into a feature similar to the println!
macro in rust to print information to the console. Therefore, after the debugging switch (flag) of the contract is turned on, the following printing can be provided:
let s = ::ink_prelude::format!("print in contract:{}", 1_u32);
::ink_env::debug_println(&s); // == println!
Set log=runtime=debug
, then start the node, You can see the logs:
Dec 01 20:02:13.307 DEBUG execute_block:apply_extrinsic: print in contract:1 {ext}
The logs printed in the node by this method are all DEBUG level, and the target
of the logs are all runtime
. But compared with println!
, the existing log
crate in Rust provides the function of printing logs at different levels (like DEBUG|INFO...), and can use the target
to filter the log, so the usage of log
in debugging contracts is more powerful than println!
.
We hope to provide the following functions in the contract by modifying FRAME contract pallet
and ink!
or providing other additional libraries:
impl Erc20 {
/// Creates a new ERC-20 contract with the specified initial supply.
#[ink(constructor)]
pub fn new(initial_supply: Balance) -> Self {
use log::{info, trace};
info!(target: "contract1", "it's info log: {}", initial_supply);
trace!(target: "contract1", "it's trace log: {}", initial_supply);
// ...
}
}
After you executed the contract, you can see the logs in node:
Dec 01 20:02:13.307 INFO contract1: it's info log:123 {ext}
Dec 01 20:02:13.307 TRACE contract2: it's trace log:123 {ext}
wasmi
interpreter to provide Backtrace
functions etc.As a WASM interpreter built by parity, wasmi
has implemented all the standard functions of WASM. But for contract developers, the execution process of wasmi
is similar to a black box. The contract developer only knows that the contract is executed by the wasmi
interpreter after deployment, but cannot know the execution status of the contract in wasmi
. If you encounter a crash such as panic
, wasmi
does not retain the current context and cannot report back the situation in the function stack
. On the other hand, WASM does not currently provide a good breakpoint debugging and Single-step debugging method, so it is not possible to interrupt during WASM execution to view the information during execution.
Therefore, we plan to fork wasmi
and provide functions similar to Backtrace
information. We may also add Breakpoint Debugging in the future
In the customized HardHat EVM
, abnormal stack traces
will be printed during the execution of the Solidity code:
Error: Transaction reverted: function selector was not recognized and there's no fallback function at ERC721Mock.<unrecognized-selector> (contracts/mocks/ERC721Mock.sol:9) at ERC721Mock._checkOnERC721Received (contracts/token/ERC721/ERC721.sol:334) at ERC721Mock._safeTransferFrom (contracts/token/ERC721/ERC721.sol:196) at ERC721Mock.safeTransferFrom (contracts/token/ERC721/ERC721.sol:179) at ERC721Mock.safeTransferFrom (contracts/token/ERC721/ERC721.sol:162) at TruffleContract.safeTransferFrom (node_modules/@nomiclabs/truffle-contract/lib/execute.js:157:24) at Context.<anonymous> (test/token/ERC721/ERC721.behavior.js:321:26)
In wasmtime
, when the inside execution of panic
appears, similar output will be printed:
Error: failed to run main module `target/wasm32-unknown-unknown/debug/bt.wasm`
Caused by:
0: failed to invoke command default
1: wasm trap: unreachable
wasm backtrace:
0: 0x1cda - <unknown>!__rust_start_panic
1: 0x1cce - <unknown>!rust_panic
2: 0x1c9e - <unknown>!std::panicking::rust_panic_with_hook::hc5713da015ebaa19
3: 0x26e - <unknown>!std::panicking::begin_panic::{{closure}}::h8e62ab0ea555186f
4: 0xfba - <unknown>!std::sys_common::backtrace::__rust_end_short_backtrace::h34a944558df1326a
5: 0x15c - <unknown>!std::panicking::begin_panic::h03c636dac2b8fb70
6: 0x1a65 - <unknown>!_start
In wasmer
, when the inside execution of panic
appears, similar output will be printed:
error: failed to run `target/wasm32-unknown-unknown/debug/bt.wasm`
│ 1: RuntimeError: unreachable
at __rust_start_panic (bt.wasm[67]:0x1cda)
at rust_panic (bt.wasm[66]:0x1cce)
at std::panicking::rust_panic_with_hook::hc5713da015ebaa19 (bt.wasm[65]:0x1c9e)
at std::panicking::begin_panic::{{closure}}::h8e62ab0ea555186f (bt.wasm[2]:0x26e)
at std::sys_common::backtrace::__rust_end_short_backtrace::h34a944558df1326a (bt.wasm[36]:0xfba)
at std::panicking::begin_panic::h03c636dac2b8fb70 (bt.wasm[0]:0x15c)
at _start (bt.wasm[55]:0x1a65)
╰─> 2: unreachable
In wasmi
, when the inside execution of panic
appears , only the following output will be printed:
thread 'main' panicked at ': Trap(Trap { kind: Unreachable })', src/bin/instantiate.rs:87:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Learning from the HardHat EVM
, wasmer
and wasmtime
, we will make similar modifications to wasmi
to achieve similar functions. We will migrate the relevant code of wasmer
and wasmtime
to develop similar functions into wasmi
.
We estimate that we should at least implement the following two parts in wasmi
in order to provide a printing function similar to BackTrace
information in wasmi
:
Name Section
in the *.wasm
source file with compilation information to provide corresponding debugging information.M1: Fork FRAME Contracts pallet
. (3 developers * 2 weeks) *
wasmi
interpreter, and attach some context information to the error and print it in the log.define_env!
macro to provide statistics on contract execution information.M2: Provide log
tools and function/macro in contracts. (3 developers * 1 weeks) *
RuntimeLogger
in Substrate Runtimedefine_env!
of FRAME Contracts Pallet
RuntimeLogger
in ink!
M3: Fork wasmi
. (3 developers * 2 weeks) *
wasmer
, wasmtime
and wasmi
Backtrace
for wasmi
M4 : Integration and testing (3 developers * 1 weeks) *
The above-mentioned forked repos are independent of Europa repo, and the corresponding functions will not be submitted to the original repo. If there are similar functions in the original repo plan, the corresponding functions will be merged after discussion with the original repo author.
wasmi
FRAME Contracts Pallet
define_env!
interface during contract execution