Patract Hub's treasury proposal for Europa (sandbox) v0.2

3yrs ago
1 Comments

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.

Europa's Future development plans

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.

Detailed design of Europa v0.2

1. Fork FRAME Contracts Pallet to provide more debug information

Although 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.

2. 2. Provide log printing during contract execution

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}

3. Fork 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:

  1. Keep the stack information of the WASM execution stack.
  2. Parse the Name Section in the *.wasm source file with compilation information to provide corresponding debugging information.

Detailed plan of v0.2 (6 weeks, 30 Nov ~ 11 Jan):

  • M1: Fork FRAME Contracts pallet. (3 developers * 2 weeks) *

    1. Enrich the error types of the module, associate more error messages related to the internal wasmi interpreter, and attach some context information to the error and print it in the log.
    2. Provide the function of tracking the contract call stack.
    3. Add Event Tracking to the function defined in the define_env! macro to provide statistics on contract execution information.
    4. Provide a function to print statistical data of a period of contract execution to the log.
  • M2: Provide log tools and function/macro in contracts. (3 developers * 1 weeks) *

    1. Provide components similar to RuntimeLogger in Substrate Runtime
    2. Provide the corresponding method in define_env! of FRAME Contracts Pallet
    3. Provide wrapper functions and other functions for RuntimeLogger in ink!
  • M3: Fork wasmi. (3 developers * 2 weeks) *

    1. Investigate the design of wasmer, wasmtime and wasmi
    2. Design and implement functions similar to 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.

Cost of v0.2 (18 developers * weeks)

  • Operating activities: $3600 ( Rent and Devices: $200 per developer * week )
  • Employee payments: $36000 ($1900 per developer * week)
  • —————————— +
  • Total Cost: $37800
  • Exchange Rate: $5.0 / DOT
  • Treasury Proposal: 7560 DOT

Verification : Github source & Youtube demo & Treasury Report

  1. Construct incorrect contracts and execute logs printing to determine whether it meets expectations
    1. Can trigger all wrong contracts in wasmi
    2. Can trigger all wrong contracts in FRAME Contracts Pallet
    3. Construct a contract to call a contract example and crash in it
  2. Display the call statistics of the define_env! interface during contract execution
  3. Execute the log printing function, provide formatted printing examples of different data, and judge whether it meets expectations
  4. Construct a contract that crashes under different conditions and record the log after execution. Then judge whether the backtrace information of the contract execution is completely printed, and check whether it matches the actual execution of the collapsed contract.
Up
Comments
No comments here