Open maciejwitowski opened 3 months ago
Notes from @snissn
Part One
According to this open ticket on the javascript (not rust) ethers repo there is a long standing issue wth error messages not properly propagating: https://github.com/ethers-io/ethers.js/discussions/2849
I've sanity checked that the rust ethers ipc code is doing a reasonable job at using the ethers library to reproduce error messages. Here is a code snippet I added to the subnet join command that still only prints out Simulation failed, transaction would fail with error: Revert(Bytes(0x))
let result = txn.call().await;
match result {
Ok(response) => {
println!("Simulation succeeded, transaction would succeed with output: {:?}", response);
},
Err(e) => {
println!("Simulation failed, transaction would fail with error: {:?}", e);
}
}
ethers rust has a deprecation warning that indicates to use alloy or foundry: https://github.com/gakonst/ethers-rs/issues/2667
Part Two When we call
ipc-cli subnet join --subnet /r314159/t410f3kfub4nv2n6ixbwo37t523upt77l4ezy2altnpy --collateral 1 --public-key b210a01080a99b57fb3ce1a9c028de45aa5685e8
and there is a transaction failure the ethers-rs error handling library ends up losing track of the error message when it calls error.into() in this line:
if i make the following diff on the line that loses the error message I correctly get the error message we are expecting:
- Ok(Response::Error { error, .. }) => return Err(error.into()),
+ Ok(Response::Error { error, .. }) =>{
+ return Err(ClientError::SerdeJson {
+ err: serde::de::Error::custom("provider error"),
+ text: error.to_string()
+ })
+ }
outputs the following:
[2024-05-02T23:51:38Z ERROR ipc_cli] main process failed: error processing command Some(Subnet(SubnetCommandsArgs {
command: Join(
JoinSubnetArgs {
from: None,
subnet: "/r314159/t410f3kfub4nv2n6ixbwo37t523upt77l4ezy2altnpy",
collateral: 1.0,
public_key: "b210a01080a99b57fb3ce1a9c028de45aa5685e8",
initial_balance: None
}
)
})): Failed to send transaction: MiddlewareError {
e: MiddlewareError(
JsonRpcClientError(
SerdeJson {
err: Error("provider error", line: 0, column: 0),
text: "(code: 1, message: failed to estimate gas: message execution failed: exit 33, revert reason: 0x015538b1000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000324d6574686f64206e6f7420616c6c6f7765642069662076616c696461746f722068617320616c7265616479206a6c77656420696e206d657373616765206661696c65642077697468206261636b74726163653a5c6e30303a20666d7468657265642077697468206261636b74726163653a5c6e30303a5c6e20696e206865786564203333290a30303a20663130313534353720286d6574686f64203338343434353038333729202d2d20636f6e747261637420726576657274656420283333290a2028292c20766d206572726f723a206d657373616765206661696c65642077697468206261636b74726163653a5c6e30303a2920290a",
vm error: message failed with backtrace:\n00: f0105457 (method 3844450837) -- contract reverted (33)\n01: f0105457 (method 6) -- contract reverted (33)\n (RetCode=33), data: None)"
}
)
)
}
From @raulk
I would approach it this way: First, I would focus on printing the JSON-RPC error as it comes through. Right now we're swalling the JSON-RPC error string, and just printing "Contract call reverted with data". Instead, if we dump the error string the user would see the same thing that cast prints out [1].
Whatever you do here must be deep enough that it automatically applies to all JSON-RPC calls we make.
This would be a first PR. Next, I would attempt to extract the revert reason from the error string and match it against all ABIs we have. Unfortunately, the errors are split across a number of contract bindings.
For example, errors for the GatewayManagerFacet are in this generated file: ipc/contracts/binding/src/gateway_manager_facet.rs:754, under enum GatewayManagerFacetErrors.
You can use the decode() method to decode from bytes into a higher type.
It's extremely impractical to check against all error structs of all contracts, so I think this will require some codegen logic in ipc/contracts/binding/build.rs + some macros.
I think this is too complex (especially because you're only getting started with the Rust side of things) and it'll probably be a timesink at the moment.
Let's punt it to later.
[1]: