Europa debugging example
#
Duplicate topicscheckout
ink!
to commit8e8fe09565ca6d2fad7701d68ff13f12deda7eed
.$ cd ink$ git checkout 8e8fe09565ca6d2fad7701d68ff13f12deda7eed -b tmp
Change the value to 0_u128 in the Transfer event under ink/examples/erc20/lib.rs:L90.
#[ink(constructor)]pub fn new(initial_supply: Balance) -> Self { //... Self::env().emit_event(Transfer { from: None, to: Some(caller), // change this from `initial_supply` to `0_u128` value: 0_u128.into() // initial_supply, }); instance}
Execute
cargo +nightly contract build --debug
to compile the contract.Use Redspot or Polkadot/Substrate Portal to deploy the contract.
Note erc20.wasm must be used here instead of erc20-opt.wasm, otherwise the wasm backtrace cannot be printed normally.
During the deployment process, you will encounter DuplicateTopics
and the Europa log looks like:
1: NestedRuntime { #... env_trace: [ seal_input(Some(0xd183512b0)), #... seal_deposit_event((Some([0x45726332303a3a5472616e736....]), None)), ], trap_reason: TrapReason::SupervisorError(DispatchError::Module { index: 5, error: 23, message: Some("DuplicateTopics") }), wasm_error: Error::WasmiExecution(Trap(Trap { kind: Host(DummyHostError) })) wasm backtrace: | ink_env::engine::on_chain::ext::deposit_event[1623] | ink_env::engine::on_chain::impls::<impl ink_env::backend::TypedEnvBackend for ink_env::engine::on_chain::EnvInstance>::emit_event[1564] | ink_env::api::emit_event::{{closure}}[1563] | <ink_env::engine::on_chain::EnvInstance as ink_env::engine::OnInstance>::on_instance[1562] | ink_env::api::emit_event[1561] | erc20::erc20::_::<impl ink_lang::events::EmitEvent<erc20::erc20::Erc20> for ink_lang::env_access::EnvAccess<<erc20::erc20::Erc20 as ink_lang::env_access::ContractEnv>::Env>>::emit_event[1685] # ... # ... | deploy[1691] β°β><unknown>[2385] , nest: [],}
- The last record in
env_trace
isseal_deposit_event
, notseal_return
. If the contract is executed normally, the last record should beseal_return
. - The second parameter of
seal_deposit_event
is None, which means that the host_function is not executed normally. For more details, please refer to the relevant implementation. - Combined with wasm backtrace, you can see that the top of the stack is deposit_event.
In summary, it can be concluded that the error occurred in the host_function of seal_deposit_event
.
#
Inconsistent balance typesAssuming that the balance on the chain is defined as u64, and the balance in ink! is defined as u128, an ordinary erc20 contract is deployed.
When reading total_supply
, the log content in Europa looks like:
1: NestedRuntime { ext_result: [failed] ExecError { error: DispatchError::Module { index: 5, error: 17, message: Some("ContractTrapped") }, origin: ErrorOrigin::Caller }, caller: 0000000000000000000000000000000000000000000000000000000000000000 (5C4hrfjw...), self_account: 2fe715301c9609c0c5ab75b24f2d8ad7dbe9671d7aebfeed80ed8963bc017955 (5D9Wkfa3...), selector: 0xdb6375a8, args: None, value: 0, gas_limit: 4999999999999, gas_left: 4999865113466, env_trace: [ seal_value_transferred(Some(0x0000000000000000)), ], wasm_error: Error::WasmiExecution(Trap(Trap { kind: Unreachable })) wasm backtrace: | core::panicking::panic_fmt.48[1956] | core::result::unwrap_failed[1057] | core::result::Result<T,E>::expect[1060] | ink_lang::dispatcher::deny_payment[1878] | call[1906] β°β><unknown>[2614] , nest: [],}
When calling tranfer
, the log in Europa looks like:
1: NestedRuntime { ext_result: [failed] ExecError { error: DispatchError::Module { index: 5, error: 17, message: Some("ContractTrapped") }, origin: ErrorOrigin::Caller }, caller: 0000000000000000000000000000000000000000000000000000000000000000 (5C4hrfjw...), self_account: 2fe715301c9609c0c5ab75b24f2d8ad7dbe9671d7aebfeed80ed8963bc017955 (5D9Wkfa3...), selector: 0xdb6375a8, args: None, value: 0, gas_limit: 4999999999999, gas_left: 4999865113466, env_trace: [ seal_value_transferred(Some(0x0000000000000000)), ], wasm_error: Error::WasmiExecution(Trap(Trap { kind: Unreachable })) wasm backtrace: | core::panicking::panic_fmt.48[1956] | core::result::unwrap_failed[1057] | core::result::Result<T,E>::expect[1060] | ink_lang::dispatcher::deny_payment[1878] | call[1906] β°β><unknown>[2614] , nest: [],}
It can be found that whether it is a read operation or a write operation, there will be a call -> deny_payment -> expect call
process. The reason is that in ink!, there are the following codes.
#[no_mangle]fn call() -> u32 { if true { ::ink_lang::deny_payment::<<Erc20 as ::ink_lang::ContractEnv>::Env>() .expect("caller transferred value even though all ink! message deny payments") } ::ink_lang::DispatchRetCode::from( <Erc20 as ::ink_lang::DispatchUsingMode>::dispatch_using_mode( ::ink_lang::DispatchMode::Call, ), ) .to_u32()}pub fn deny_payment<E>() -> Result<()>where E: Environment,{ let transferred = ink_env::transferred_balance::<E>() .expect("encountered error while querying transferred balance"); if transferred != <E as Environment>::Balance::from(0u32) { return Err(DispatchError::PaidUnpayableMessage) } Ok(())}
In ink!
, the timing of expect
is different for off_chain
and on_chain
. In off_chain
, it is because ink_env::transferred_balance::<E>()
cannot be decoded correctly. In on_chain
,deny_payment
returns Error due totransferred!=0
, and expect
appears in call
.It can be seen that the current ink!
in off_chain
and on_chain
is not exactly the same for some situations, causing debugging troubles.