Skip to main content

Europa教程

背景信息#

Europa作为一个模拟具备合约功能的节点沙盒环境,其接口(主要是RPC)对于大部分第三方工具都保持兼容,因此可以将Europa视为一个独立的节点进行操作。

搭建开发环境#

Europa的环境与正常使用节点调试合约的环境大致相同,唯一的差别在于如果需要打印Wasm的backtrace时,需要使用Patract提供的一个fork版本的cargo-contract,直到官方的cargo-contract合并Patract提交的功能之前。如果不需要打印合约执行崩溃时的Wasm backtract,则使用官方提供的cargo-contract即可。

  • 编译并运行 Europa 节点
$ git clone --recurse-submodules https://github.com/patractlabs/europa.git## or do following commands$ git clone https://github.com/patractlabs/europa.git$ cd europa/vendor$ git submodule update --init --recursive

也可直接使用cargo install的方式安装Europa,但是要添加上--locked以使用Europa当前依赖的Substrate版本。

$ cargo install europa --git=https://github.com/patractlabs/europa.git --force --locked

运行Europa

$ ./target/release/europa --log=runtime=debug -d ./europa_database# If there is no need to retain data, you can also use `--tmp` to run Europa$ ./target/release/europa --log=runtime=debug --tmp
$ cargo install cargo-contract --git https://github.com/patractlabs/cargo-contract --branch=tag-v0.12.1 --force

如果您已经安装了官方的cargo-contract且不想覆盖安装,可以采取手动编译的方式。

$ git clone https://github.com/patractlabs/cargo-contract --branch=tag-v0.12.1$ cd cargo-contract$ cargo build --release
  • 编译合约

--debug选项由Patract的cargo-contract提供,若使用官方提供的cargo-contract则以下执行命令中都不需要--debug的选项。

$ RUSTUP_TOOLCHAIN=nightly cargo-contract contract build --debug# or$ cargo +nightly contract build --debug

-d/--debug能够在target/ink目录下替换原本的*.wasm*.contract文件,替换后的Wasm、Contract文件关闭了编译过程中的代码优化条件,且包含了name section部分,用来帮助分析Wasm调用栈的信息。

如果在编译合约的时候没有使用Patract仓库中的cargo-contract,并携带-d/--debug参数进行合约编译,则在合约执行过程中若出现Wasm panic时,可能出现如下日志打印。

wasm_error: Error::WasmiExecution(Trap(Trap { kind: Unreachable }))   wasm backtrace:   |  <unknown>[...]   |  <unknown>[...]   ╰─><unknown>[...]

添加了-d/--debug后产生的编译文件一般是原文件的几百倍。因为新文件没有进行优化,且保留了大量调试信息。因此您也可以通过文件大小粗略判定是否是添加了-d/--debug选项后产生的文件。

部署合约#

您可以使用Redspot或者Substrate Protal来部署合约。

Europa的extending types如下。

{  "LookupSource": "MultiAddress",  "Address": "MultiAddress"}

例如使用Redspot部署,使用apps执行交易和查看状态。

  1. 使用Redspot部署一个合约。
$ npx redspot run scripts/deploy.js
  1. 获取到部署成功的合约地址,在apps上添加一个已存在的合约。

分析日志#

使用Europa部署及执行合约的过程中会有详细日志的打印,您可以根据这些日志快速定位出合约中出现的问题。通过这些日志,合约的执行过程就不再是一个黑盒。

日志打印示例如下。

1: NestedRuntime {    ext_result: [success] ExecReturnValue { flags: 0, data:  },    caller: d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d (5GrwvaEF...),    self_account: 0144d6fc570d7bddda6f8e36141f179cd172324599b556ef514193f3105865f6 (5C6NMXaS...),    selector: 0x9bae9d5e,    args: 0x40420f00000000000000000000000000,    value: 10000000000000000,    gas_limit: 200000000000,    gas_left: 190018947968,    env_trace: [        seal_input(Some(0x9bae9d5e40420f00000000000000000000000000)),        seal_caller(Some(0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d)),        ...       seal_set_storage((Some(0x0300000001000000000000000000000000000000000000000000000000000000), Some(0x000000000000000000000000))),    ],    sandbox_result_ok: Value(        I32(            0,        ),    ),    nest: [],}

Contract执行日志#

根据上文日志,可以分析出以下信息。

  • ext_result:合约调用执行的执行结果(通过交易调用与RPC调用都属于合约调用)。
  • caller:调用者的公钥,合约调用合约则为父合约的公钥(与EVM的模型一致)。
  • self_account:本合约的地址。
  • selector: 被调用的方法的selector,通过这个属性可以判断出这次的调用是合约的哪个方法。
  • argsvaluegas_limitgas_limit等表明了这次执行的相关参数及gas消耗。
  • env_trace、sandbox_result_ok:合约Wasm执行与pallet-contracts之间的交互信息,以及Wasm执行器最终的结果(Wasm执行器结果与合约执行结果是不同概念)。
  • nest:描述了合约调用合约的关系,由于这里为空,表面这次调用只涉及一个合约执行。详细介绍见后文。

根据Europa提供的合约日志,您可以看出一次合约调用的详细过程。如果您对合约模块pallet-contracts比较了解,则可以获得许多重要的调试信息以辅助定位合约问题。若您对合约模块了解较少,selectorcallernest等信息也能给您带来很大帮助,减少调试合约的时间。

注意 当在apps上查看contracts中的messages时,apps会自动调用合约只读的messages获取当前合约的一些值,导致Europa会出现一些读取调用的日志,干扰正常判断。因此您需要辨别清楚哪块日志才是自己所需要的。 若使用发送请求都是能被自己控制的第三方客户端,则没有这方面的顾虑。

使用apps发送请求时,如何在Europa中辨别出需要的日志?#

NestedRuntime {}块下有一个selector字段,表示该次合约执行所使用的selector。您可以在metadata.json 中的messages部分中,获知当前调用的方法名对应的selector是什么,示例如下。

"messages": [    {      "name": [        "flip"      ],      "selector": "0x633aa551"    }]

因此可以通过selector字段与日志中的selector进行比对,判定出当前通过apps发出的合约调用所对应的日志部分。

wasmi panic backtrace#

假设在ink!中编写合约的方法如下。

#[ink(message)]pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> {    let from = self.env().caller();    self.transfer_from_to(from, to, value)?;    panic!("123");    Ok(())}

调用该方法时,Europa中会如下日志打印。

1: NestedRuntime {        ext_result: [failed] ExecError { error: DispatchError::Module {index:5, error:17, message: Some("ContractTrapped"), orign: ErrorOrigin::Caller }}    caller: d43593c715fdd31c61141abd04a99fd6822...(5GrwvaEF...),    self_account: b6484f58b7b939e93fff7dc10a654af7e.... (5GBi41bY...),    selector: 0xfae3a09d,    args: 0x1cbd2d43530a44705ad088af313e18f80b5....,    value: 0,    gas_limit: 409568000000,    gas_left: 369902872067,    env_trace: [        seal_value_transferred(Some(0x00000000000000000000000000000000)),        seal_input(Some(0xfae3a09d1cbd.....)),        seal_get_storage((Some(0x0100000000000....), Some(0x010000000100000001000000))),        # ...        seal_caller(Some(0xd43593c715fdd31c61141abd...)),        seal_hash_blake256((Some(0x696e6b20686173....), Some(0x0873b31b7a3cf....))),              # ...          seal_deposit_event((Some([0x45726332303a....00000000000]), Some(0x000..))),    ],        trap_reason: TrapReason::SupervisorError(DispatchError::Module { index: 5, error: 17, message: Some("ContractTrapped") }),    wasm_error: Error::WasmiExecution(Trap(Trap { kind: Unreachable }))        wasm backtrace:         |  core::panicking::panic[28]        |  erc20::erc20::_::<impl erc20::erc20::Erc20>::transfer[1697]        |  <erc20::erc20::_::__ink_Msg<[(); 2644567034]> as ink_lang::traits::MessageMut>::CALLABLE::{{closure}}[611]        |  core::ops::function::FnOnce::call_once[610]        |  <erc20::erc20::_::_::__ink_MessageDispatchEnum as ink_lang::dispatcher::Execute>::execute::{{closure}}[1675]        |  ink_lang::dispatcher::execute_message_mut[1674]        |  <erc20::erc20::_::_::__ink_MessageDispatchEnum as ink_lang::dispatcher::Execute>::execute[1692]        |  erc20::erc20::_::<impl ink_lang::contract::DispatchUsingMode for erc20::erc20::Erc20>::dispatch_using_mode[1690]        |  call[1691]        ╰─><unknown>[2387]    ,    nest: [],}

注意 当前该合约需要使用Patract的cargo-contract才能打印Wasm的Backtrace。

从Europa的日志中,可以分析出如下调用过程。

call -> dispatch_using_mode -> ... -> transfer -> panic 

因此您可以定位到产生这次panic的原因是因为transfer这个函数中出现了panic导致。

自定义ChainExtensions#

ink logger#

查看ink-log

ZKP feature#

查看zkMega,相关合约示例请参见metis/groth16