When regarding Europa as a sandbox to run ink!, ask! and Solang contracts, it is a very awesome and useful tool. We design many features to help developers to locate their bugs and errors in their contracts.
Though the contracts framework (like ink!
) may already provide a completed mocked environment to run test case, but
the mocked environment is different from the chain environment eventually. For example the mocked environment is hard to
debug the situation that one contract call another contract, however in fact, those cases are common in defi contracts.
And in another word, the prosperous ecology is not relayed on a single contract, it relies on the combination of different
contracts.
- Contract execution event tracker
- WASM panic backtrace
- Self ChainExtension
- Contract logger
- ZKP feature
We do following things to support those features:
-
Modification on the
pallet-contracts
layerBy adding trace during the execution of the contract by
pallet-contracts
, the information in the contract layer is recorded, especially when a contract all another contract. The recorded information is mapping with the contract stack, could be analysed the relationship between contracts. On the other hand, the error message of calling WASM execution is improved. -
Modification on the
wasmi
layer We have provided the backtrace function of recording wasm execution forwasmi
, and provided support forparity-wasm
,pwasm-utils
, andcargo-contract
during wasm processing of the contract contains the function of the name section. The name section also will provide the basic requirement to debug WASM contracts by gdb/lldb in future. -
ChainExtension:
- Contract logger We integrate the lib which is provided by Patract: ink-log. This lib pass log data from contract to Europa through ChainExtensions.
- ZKP feature We integrate the lib which is provided by Patract: megaclite. This lib providers different curves to support basic function to run Groth16 algorithm in contracts.
For using all features when running contracts in Europa, we advice developers use Patract's cargo-contract
to compile ink! contract, until this pr#131 Enable debug info to the source warehouse with flag in command build could be merged by paritytech.
In Patract's cargo-contract
, we will contain the "name section" while compile contracts. Before this PR is merged,
currently, only the cargo-contract
version provided by us (Patract) can be used:
cargo install --git https://github.com/patractlabs/cargo-contract --branch v0.10.0 --force
If you do not want this version of cargo-contract
to override the version released by paritytech, then it is recommended
to compile locally and use the compiled cargo-contract
directly:
git clone https://github.com/patractlabs/cargo-contract --branch v0.10.0
cd cargo-contract
cargo build --release
Note: Executing the
cargo-contract build
command requires thedefault toolchain
of the rust toolchain to benightly
, otherwise you can only usecargo +nightly contract build
, but usingcargo
to callcargo-contract
needs to be executedcargo install
installs or overwrites the compiled product in the~/.cargo/bin
directory, and cannot co-exist with paritytech'scargo-contract
Execute:
cargo-contract build --help
# or
cargo +nightly contract build --help
If you can see:
FLAGS:
-d, --debug
Emits debug info into wasm file
It means that you are using the cargo-contract
provided by Patract. If you want to see the backtrace of the WASM
contract execution crash while using Europa, you need to add the --debug
command when compiling the contract.
Using the --debug
command will generate file in the target/ink
directory of the originally compiled contract,
replacing original *.wasm/*.contract
file. Notice that the file generated by this method is usually much larger than the original file, for the *.wasm/*.contract
file is the WASM contract file containing the "name section" part.
If you need to use Europa for testing, the contract deployed to Europa needs to use the file *.wasm/*.contract
compiled by adding -d/--debug
instead of the originally generated file.
In following doc, about the log part, if the contract do not have "name section" (contracts are not compiled by
--debug
), the output may contain a batch of<unknown>
. If you meet this, please use the contract which has "name section".wasm_error: Error::WasmiExecution(Trap(Trap { kind: Unreachable })) wasm backtrace: | <unknown>[...] | <unknown>[...] ╰─><unknown>[...]
In our forked pallet-contracts
, we define the struct NestedRuntime
to track the event when developers execute contracts:
/// Record the contract execution context.
pub struct NestedRuntime {
/// Current depth
depth: usize,
/// The current contract execute result
ext_result: ExecResultTrace,
/// The value in sandbox successful result
sandbox_result_ok: Option<ReturnValue>,
/// Who call the current contract
caller: AccountId32,
/// The account of the current contract
self_account: Option<AccountId32>,
/// The input selector
selector: Option<HexVec>,
/// The input arguments
args: Option<HexVec>,
/// The value in call or the endowment in instantiate
value: u128,
/// The gas limit when this contract is called
gas_limit: Gas,
/// The gas left when this contract return
gas_left: Gas,
/// The host function call stack
env_trace: EnvTraceList,
/// The error in wasm
wasm_error: Option<WasmErrorWrapper>,
/// The trap in host function execution
trap_reason: Option<TrapReason>,
/// Nested contract execution context
nest: Vec<NestedRuntime>,
}
Currently, the recorded information in this struct is printed every time while the contract be executed (including from rpc call or extrinsic). In the future, this data could be stored in local and access by rpc call for 3rd-parity client, which can be used for visualized presentation to show the detailed information in execution contract.
In the model of pallet-contracts
, a contract calling another contract is in the "contract stack" model, so NestedRuntime
will track the execution process of the entire contract stack, and use the property of nest
to store a list of NestedRuntime
to represent other contracts the the contract called.
In the process of executing a contract by pallet-contracts
, Europa records the relevant information in the execution
process in the structure of NestedRuntime
in the form of a bypass, and will print the NestedRuntime
to the log
(show the case later) in a certain format after the contract call ends. Contract developers can analyze the information
printed by NestedRuntime
to obtain various detailed information during the execution of the contract, which can be used
in various situations:
- help to locate where the error occurs, including the following situations:
pallet-contracts
layerink!
layer- The specific position in the contract layer
- Locate which level of the contract is when a contract calling another contract
- Analyze the information during the execution of the contract at this timing:
- Analyze the consumption of gas execution
- Analyze the call of
get_storage
andset_storage
, help reconstruct the contract code and analyze the demand ofrent
- According to
selector
,args
andvalue
, analyze and locate whether the transaction parameters of the third-party SDK are legal. - Analyze the execution path of the contract and adjust the contract based on the
nest
information and combined with theseal_call
information. - etc.
The process of recording pallet-contracts
executing contract to NestedRuntime
is relatively fine-grained.
The process of logging the information of the execution contract of pallet-contracts
to NestedRuntime
is relatively fine-grained. Take seal_call
in define_env!
as an example:
pub struct SealCall {
callee: Option<HexVec>,
gas: u64,
value: Option<u128>,
input: Option<HexVec>,
output: Option<HexVec>,
}
The attributes are basically Option<>
. For example, before calling the contract, the input
will be set to Some
,
and the return value will be set after the calling contract is normal. If there is an error in the calling contract,
then output
will remain None
. Therefore, if input
is Some
and output
is None
, it means that there is a
problem with the called contract during the process of calling the contract.
The example log print like this, this log is printed when the ink/example/flipper
contract's get
message is called
by rpc request contracts_call
:
1: NestedRuntime {
ext_result: [success] ExecReturnValue { flags: 0, data: 01 },
caller: 0000000000000000000000000000000000000000000000000000000000000000 (5C4hrfjw...),
self_account: 3790ddf4d8c63d559b3b46b96ca9b7b5f07b772c9ad4587eca6c0738e5d48422 (5DKZXRQN...),
selector: 0x1e5ca456,
args: None,
value: 0,
gas_limit: 4999999999999,
gas_left: 4998334662707,
env_trace: [
seal_value_transferred(Some(0x00000000000000000000000000000000)),
seal_input(Some(0x1e5ca456)),
seal_get_storage((Some(0x0000000000000000000000000000000000000000000000000000000000000000), Some(0x01))),
seal_return((0, Some(0x01))),
],
trap_reason: TrapReason::Return(ReturnData { flags: 0, data: 01 }),
nest: [],
}
Let's explain the information printed above:
-
ext_result
: indicates that this contract call is displayed as successful or failed:[success]
: indicates the successful execution of this contract (Note: the successful execution of the contract does not mean the successful execution of the business logic of the contract itself. There may be an error return inink!
or the business logic of the contract itself, as in case 3 in the following text.) And theExecResultValue {flag:0, data: 0x...}
followed by[success]
indicates the return value after this contract is executed.[failed]
: indicates that the execution of this contract failed, and theExecError {.. }
followed by[failed]
indicates the cause of this error. The reason for this is the value recorded inevent
on the chain, which is the value defined indecl_error!
ofpallet-contracts
.
-
1: NestedRuntime
&nest
: The contract information that represents the current print information is located in the first layer of the contract call stack. If the current contract calls another contract, it will appear in the array of thenest
field.2: NestedRuntime
and1: NestedRuntime
has the same structure. Among them,2
indicates that the called contract is in the second layer of the contract call stack. If several contracts are called across contracts in the current contract, there will be severalNestedRuntime
in the array ofnest
. If there are other contract calls in the second-level contract, the same goes for.For example, if there are contracts A, B, C, if it is the following situation:
-
After A calls B, it returns to A to continue execution, and then calls contract C
Then it will produce a log print similar to the following:
1: NestedRuntime { self_account: A, nest:[ 2: NestedRuntime { self_account: B, nest:[], }, 2: NestedRuntime { self_account: C, nest:[], } ] }
-
After A calls B, B calls contract C again, and finally returns to A
Then it will produce a log print similar to the following:
1: NestedRuntime { self_account: A, nest:[ 2: NestedRuntime { self_account: B, nest:[ 3: NestedRuntime { self_account: C, nest:[], } ], } ] }
-
-
caller
: who is the caller of the current contract. If the contract calls the contract, the value of the called contract is the address of the upper-level contract. (The addr is0x000000...
for this example is called by rpc.) -
self_account
: represents the address of the current contract itself. -
selector
&args
&value
: Represents theselector
and parameters passed in when calling the current contract. These information can quickly locate whether the calling contract method is correct. -
gas_limit
&gas_left
: Represents thegas_limit
passed in when the contract is currently called and the remaining gas after executing this layer. Note here thatgas_left
refers to the remaining gas after the execution of this layer of contract, so In the contract call contract, the gas consumed by each layer of contract can be determined throughgas_left
, not only get the consumption after the execution of the entire contract. -
env_trace
: Indicates that during the execution of the current layer of the contract, each time host_function is called in the contract WASM execution, a record will be added to the list here. Because all host_functions and the definitions indefine_env!
in thepallet-contracts
module are related, so tracingenv_trace
can trace the process of interacting withpallet-contracts
during the execution of the current WASM contract.For example, if following thing appears in
env_trace
:seal_call
: It means that there is a contract call contract situation in the current contract. According to the order in whichseal_call
appears inenv_trace
, it can correspond tonest
to calculate the state before and after the contract calls the contract.seal_get_storage
&seal_set_storage
: It means that data read and write occurred in the contract. Through these two interfaces, it is possible to intercept and count the data read and write during the execution of the current contract, and the data size calculated byseal_set_storage
can also be used to infer the storage size required byrent
.seal_deposit_event
: indicates that the event is printed in the contract. Here you can intercept the content of each event separately, instead of getting a unified event at the end. And the following text will use an example to surface that Europa can quickly locate the bug in thehost_function
.
On the other hand, the statistics of
env_trace
are relatively fine-grained. For example, if there are multiple possible errors inhost_function
, when an error occurs, all the information before the error will be retained, so it can be located to the place where the problem occurred during the execution ofhost_function
.And if there is an error in
host_function
that causes the contract to end execution,env_trace
records the last errorhost_function
call, so you can directly locate whichhost_function
caused the contract execution exception. -
trap_reason
: According to the definition ofTrapReason
inpallet-contracts
,trap_reason
can be divided into 2 categories:Return
&Termination
&Restoration
: indicates that the contract exit is the design ofpallet-contracts
, not an internal error. This type of trap indicates that the contract is executed normally and is not an error.SupervisorError
: Indicates that an error occurred during the execution of the contract calling host_function.
Therefore, the current Europa log printing design is designed to record whenever
trap_reason
appears. On the other hand,trap_reason
may not always appear during the execution of the contract. Combining the design ofpallet-contracts
andink!
, there is a case where the successful execution of the contract or the execution failure in theink!
layer does not generatetrap_reason
. Therefore, in addition to recordingtrap_reason
, Europa also records the results returned by the WASM executor after execution, which is recorded withsandbox_result_ok
. -
sandbox_result_ok
: The value ofsandbox_result_ok
represents the result of the contract after the WASM executor is executed. This value could have been recorded assandbox_result
, including correct and incorrect conditions. However, due to the limitations of Rust and combined with the business logic ofpallet-contracts
, only the result ofsandbox_result
is kept asOk
here. For log printing, Europa is designed to printsandbox_result_ok
only when trap_reason is the first case, as information to assist in judging contract execution.sandbox_result_ok
is the WASM executor result after callinginvoke
After the processing ofto_execution_result
, if there is notrap_reason
, the result ofOk(..)
is discarded. But in fact there are two situations here:- An error occurred in
ink!
: According to the implementation ofink!
, before calling the functions wrapped by the contract#[ink(message)]
and#[ink(constructor)]
, the input The process of decoding and matchingselector
. If an error occurs during this process, the contract will return error codeDispatchError
. But for the WASM executor, the WASM code is executed normally, so the result will be returned, including this error code. This contract execution process is an error situation. - The return value of
#[ink(message)]
is defined as()
: According to the implementation ofink!
, if the return value type is()
,seal_reason
will not be called, so it will not Containstrap_reason
. This contract execution process is an correct situation.
Since
ink!
is only a contract implementation that runs onpallet-contracts
, other implementations may have different rules, so currentlysandbox_result_ok
is only used to assist in determining the execution of theink!
contract, the value isReturnValue
. Among them, if the<num>
part ofReturnValue::Value(<num>)
of the log is not 0, it means that there may be an error in the execution ofink!
. You can useink!
forDispatchError
The error code determines the error. - An error occurred in
-
wasm_error
: indicates the backtrace when WASM executes an error. This part will be printed only whenext_result
isfailed
.The rpc call in this example is called normally, thus there is no
wasm_error
field. We will show more example later.
We forked wasmi and integrated it into ep-sandbox
. Forked pallet-contracts
can obtain the error information of forked wasmi
through ep-sandbox
, including the backtrace information of wasmi
.
If you need to make wasmi
can retain the backtrace information during execution, you need to have the following functions:
- The "name section" section is required in the WASM source file (see the specification of name section))
- Keep the "name section" information in the process of checking the contract by
pallet-contracts
and still have a corresponding relationship with the wasm source file after the process. - During the execution of
wasmi
, the execution stack needs to be preserved with the key information of the functions. At the same time, the "name section" needs to be parsed and correspond to the function information reserved by thewasmi
execution stack.
The changes to 2 involve cargo-build
and parity-wasm
, while the changes to 1 and 3 are mainly in the forked wasmi
, and a small part involves pwasm-utils
.
And in all, we create following pr to the origin repo:
cargo-contract
parity-wasm
wasm-utils
- PR: paritytech/wasm-utils#146
- Source: patractlabs/wasm-utils#146
wasmi
- PR: No pr for this repo yet
- Source: patractlabs/wasmi
For example, we modify the example contract ink/example/erc20
in the ink! project as follows:
#[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(())
}
WASM, it corresponds to the code after the macro expansion of the original file, so if you want to compare the errors of the call stack, you need to expand the macro of the original contract:
cargo install expand
cd ink/example/erc20
cargo expand > tmp.rs
After reading the tmp.rs
file, we can know that WASM needs to go through when it executes the transfer
function:
fn call() -> u32
-> <Erc20 as ::ink_lang::DispatchUsingMode>::dispatch_using_mode(...)
-> <<Erc20 as ::ink_lang::ConstructorDispatcher>::Type as ::ink_lang::Execute>::execute(..) # compile selector at here
-> ::ink_lang::execute_message_mut
-> move |state: &mut Erc20| { ... } # a closure
-> <__ink_Msg<[(); 2644567034usize]> as ::ink_lang::MessageMut>::CALLABLE
-> transfer
Therefore, if the panic
in transfer
is encountered during the contract call, the backtrace of WASM should be similar to this.
After putting code and deploying the contract, if developer calls the transfer
message, the Europa will print:
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: [],
}
In the above example, because the execution of transfer
will trigger panic
, you can see that the cause of the error
here is WasmiExecution(Trap(Trap {kind: Unreachable }))
, indicating that this time the failure is due to execution
The situation of Unreacble
in the contract process is caused, and the backtrace information below also very accurately describes
the function execution call stack when an error is encountered after the expansion of the contract macro discussed above.
The following calling process can be clearly found from the backtrace.
call -> dispatch_using_mode -> ... -> transfer -> panic
This process is consistent with the original information of the contract.
More information refers to ink-log.
More information refers to megaclite, and the example contracts in metis/groth16.
Some time ago, we (Patract) reported a bug to ink!
, see issue:"When set '0' value in contracts event, may cause Error::ContractTrapped
and panic in contract #589" . It is very difficult to locate this error before Europa has developed the relevant function. Thank you @athei located the error. Here we reproduce this error and use Europa's log to quickly analyze and locate the place where the bug appears:
-
checkout
ink!
to commit8e8fe09565ca6d2fad7701d68ff13f12deda7eed
cd ink git checkout 8e8fe09565ca6d2fad7701d68ff13f12deda7eed -b tmp
-
Go in
ink/examples/erc20/lib.rs:L90
to changevalue
to0_u128
inTransfer
#[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 this contract ( Note that you must use the erc20.src.wasm file)
You should encounter DuplicateTopics
in the deployment phase (before this bug is corrected, the reported error is ContractTrap
), and in the Europa log Will show:
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: [],
}
You can see from the above log:
- The last record of
env_trace
isseal_deposit_event
instead ofseal_return
(when the contract is executed correctly, the last record must beseal_return
) - The second parameter of
seal_deposit_event
isNone
instead of an existing value, which indicates that the host_function ofseal_deposit_event
has not been executed, but an error occurred during the execution (see the forked dependency of Europa) See the [corresponding implementation] (https://github.com/patractlabs/substrate/blob/3624deb47cabe6f6cd44ec2c49c6ae5a29fd2198/frame/contracts/src/wasm/runtime.rs#L1399) for the source code of the version ofPallet Contracts
. - Combined with the error stack of wasm backtrace, we can intuitively see that the top call stack of backtrace is
deposit_event
.
Therefore, combining the above information, we can directly infer that the host_function of seal_deposit_event
has an exception during the execution. (Before the submission of Pallet Contracts
pull#7762, we recorded the error message in host_function. After the merge, we used trap_reason
unified error message.)
If the chain uses the definition of Balance=u64
, and the definition of Balance
in the chain is unknown to ink!
(the default definition of Balance is u128
). Therefore, when using u128
to define Balance
's ink!
as a dependency compiled contract, when running on a chain where Balance
is defined as u64
, it will cause the Pallet Contracts
module to transfer values to the contract , The contract internally regards the value
of u64
as a decoding error of u128
.
Take the example contract of erc20 as an example, after expanding the macro of the contract, you can see:
In the call of call
, since deny_payment
is checked before calling dispatch_using_mode
, and if an Error is returned when checking deny_payment
, it will be directly panic
.
Therefore, in this case, the contract for deploying (Instantiate
) ERC20 will execute normally, and any method of ERC20 such as transfer
will be called with ContractTrap
.
The call
stage, such as calling transfer
:
Calling transfer
to the above successfully instantiated function, ContractTrap
will appear, Europa's log shows as follows:
1: NestedRuntime {
ext_result: [failed] ExecError { error: DispatchError::Module {index:5, error:17, message: Some("ContractTrapped"), orign: ErrorOrigin::Caller }}
# ...
env_trace: [
seal_value_transferred(Some(0x0000000000000000)),
],
wasm_error: Error::WasmiExecution(Trap(Trap { kind: Unreachable }))
wasm backtrace:
| core::panicking::panic_fmt.60[1743]
| core::result::unwrap_failed[914]
| core::result::Result<T,E>::expect[915]
| ink_lang::dispatcher::deny_payment[1664]
| call[1691]
╰─><unknown>[2387]
,
nest: [],
}
First notice that the last record of env_trace
is still not seal_return
, and the error cause of wasm_error
is WasmiExecution::Unreachable
. Therefore, it can be determined that panic
or expect
was encountered during the execution of the contract.
From the wasm backtrace, it is very obvious that the execution process is
call -> deny_payment -> expect
According to the code expanded macro (cd ink/examples/erc20; cargo expand> tmp.rs
), we can see:
#[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()
}
Therefore, it can be judged that an error was returned in deny_payment
during the execution of the contract in the process of transfer
, and the direct processing of the error as expect
resulted in the execution result of wasmi
being Unreachable
Tracking the code of deny_payment
can find that the function returns expect
caused by Error
Note,The relevant code is as follows:
In
ink_lang
https://github.com/paritytech/ink/blob/master/crates/lang/src/dispatcher.rs#L140-L150pub 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(()) }There will be a difference between the
off_chain
part and theon_chain
part in the ink,off_chain
will think that an error is returned at the stage ofink_env::transferred_balance::<E>()
, so it is executingAfter transferred_balance
, you will encounterexpect
which leads topanic
, and part ofon_chain
is taken from the memory of wasm, it will normally get the characters corresponding to u128 length and decode to gettransferred
, which is just decoded The result will not meet expectations, causingtransferred!=0
to makedeny_payment
return an Error, and the part wheredeny_payment
is called in the macro expansion of the contract triggersexpect
if true { ::ink_lang::deny_payment::<<Erc20 as ::ink_lang::ContractEnv>::Env>() .expect("caller transferred value even though all ink! message deny payments") }Therefore, for wasm backtrace,
expect
appears whendeny_payment
is called incall
, not whentransferred_balance
is called indeny_payment
.This example side shows that
ink!
currently does not completely correspond to the processing ofoff_chain
andon_chain
, and may cause difficult-to-check errors for contract users in some cases