Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context to multitest execution errors #597

Merged
merged 10 commits into from
Jan 4, 2022
Merged

Add context to multitest execution errors #597

merged 10 commits into from
Jan 4, 2022

Conversation

uint
Copy link
Contributor

@uint uint commented Dec 22, 2021

Closes #575

let address = env.contract.address.clone();
(self.execute_fn)(deps, env, info.clone(), msg.clone())
.map_err(|err| anyhow!("{}", err))
.context(format!(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ueco-jb This stuff takes the ContractError you'd normally return in a contract entrypoint, makes an anyhow::Error out of it, and then wraps that in another anyhow::Error, now with extra info (address, sender, etc). So we're not getting extra stdout logs, it's just the error now has more info.

If you look at the failing test, it refers to places like this in code: https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw3-flex-multisig/src/contract.rs#L1089. Basically it looks like downcast stopped working with the added context, and I'm not sure why yet.

If you're happy with the direction though, I'll investigate when I'm back.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding context to the error does seem like a very nice way of handling it.

I assume this context is print out when we do res.unwrap() on the Err variant. If so, this seems like a very nice addition.

Question: if this is nested - execute calls execute in submsg, 2nd contract errors - this will show context for both contracts, right?

A test showing the error message in such a case would be great to evaluate it. (I know... super hard to do CI there, maybe just something marked "#[skip]" that I can modify that line and run locally to evaluate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: if this is nested - execute calls execute in submsg, 2nd contract errors - this will show context for both contracts, right?

Yes, although I haven't tested it with more levels of nesting I think. A test would be nice for that. I'll look at that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, the nested thing didn't quite work, but I fixed it. There are tests showing it works. There's a way to take a peek what the display looks like now: https://github.com/CosmWasm/cw-plus/pull/597/files#r778142632

Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment for more details, but if it works as I think it does, I like the direction

let address = env.contract.address.clone();
(self.execute_fn)(deps, env, info.clone(), msg.clone())
.map_err(|err| anyhow!("{}", err))
.context(format!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding context to the error does seem like a very nice way of handling it.

I assume this context is print out when we do res.unwrap() on the Err variant. If so, this seems like a very nice addition.

Question: if this is nested - execute calls execute in submsg, 2nd contract errors - this will show context for both contracts, right?

A test showing the error message in such a case would be great to evaluate it. (I know... super hard to do CI there, maybe just something marked "#[skip]" that I can modify that line and run locally to evaluate

@uint uint marked this pull request as ready for review January 4, 2022 14:51
.unwrap_err();

// uncomment to have the test fail and see how the error stringifies
// panic!("{:?}", err);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A dirty way to take a peek at the debug formatted error with two levels of nesting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get:

---- app::test::errors::double_nested_call stdout ----
thread 'app::test::errors::double_nested_call' panicked at 'error executing WasmMsg:
sender: random
Execute { contract_addr: "Contract #0", msg: Binary([123, 34, 101, 120, 101, 99, 117, 116, 101, 34, 58, 123, 34, 99, 111, 110, 116, 114, 97, 99, 116, 95, 97, 100, 100, 114, 34, 58, 34, 67, 111, 110, 116, 114, 97, 99, 116, 32, 35, 49, 34, 44, 34, 109, 115, 103, 34, 58, 34, 101, 121, 74, 108, 101, 71, 86, 106, 100, 88, 82, 108, 73, 106, 112, 55, 73, 109, 78, 118, 98, 110, 82, 121, 89, 87, 78, 48, 88, 50, 70, 107, 90, 72, 73, 105, 79, 105, 74, 68, 98, 50, 53, 48, 99, 109, 70, 106, 100, 67, 65, 106, 77, 105, 73, 115, 73, 109, 49, 122, 90, 121, 73, 54, 73, 109, 85, 122, 77, 68, 48, 105, 76, 67, 74, 109, 100, 87, 53, 107, 99, 121, 73, 54, 87, 49, 49, 57, 102, 81, 61, 61, 34, 44, 34, 102, 117, 110, 100, 115, 34, 58, 91, 93, 125, 125]), funds: [] }

Caused by:
    0: error executing WasmMsg:
       sender: Contract #0
       Execute { contract_addr: "Contract #1", msg: Binary([123, 34, 101, 120, 101, 99, 117, 116, 101, 34, 58, 123, 34, 99, 111, 110, 116, 114, 97, 99, 116, 95, 97, 100, 100, 114, 34, 58, 34, 67, 111, 110, 116, 114, 97, 99, 116, 32, 35, 50, 34, 44, 34, 109, 115, 103, 34, 58, 34, 101, 51, 48, 61, 34, 44, 34, 102, 117, 110, 100, 115, 34, 58, 91, 93, 125, 125]), funds: [] }
    1: error executing WasmMsg:
       sender: Contract #1
       Execute { contract_addr: "Contract #2", msg: Binary([123, 125]), funds: [] }
    2: Contract returned an error on execute msg:
       EmptyMsg
    3: Generic error: Handle failed', packages/multi-test/src/app.rs:2693:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great output.
I just also realise we need a better debug encoding for Binary.

Added CosmWasm/cosmwasm#1199 which could be an interesting follow-up sometime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! It's probably not super important that the binary stuff is readable here - it's usually going to be something predictable like WasmMsg::Execute, and then the contract execute msg itself will be shown in its deserialized form. Like 2: in your output.

Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff.

A few items for follow-up (pretty sure they were on your plate, but double-checking) but a great improvement.

.instantiate_contract(error_code_id, owner, &msg, &[], "error", None)
.unwrap();

// caller1 calls caller2, caller2 calls error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good demo

.unwrap_err();

// uncomment to have the test fail and see how the error stringifies
// panic!("{:?}", err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get:

---- app::test::errors::double_nested_call stdout ----
thread 'app::test::errors::double_nested_call' panicked at 'error executing WasmMsg:
sender: random
Execute { contract_addr: "Contract #0", msg: Binary([123, 34, 101, 120, 101, 99, 117, 116, 101, 34, 58, 123, 34, 99, 111, 110, 116, 114, 97, 99, 116, 95, 97, 100, 100, 114, 34, 58, 34, 67, 111, 110, 116, 114, 97, 99, 116, 32, 35, 49, 34, 44, 34, 109, 115, 103, 34, 58, 34, 101, 121, 74, 108, 101, 71, 86, 106, 100, 88, 82, 108, 73, 106, 112, 55, 73, 109, 78, 118, 98, 110, 82, 121, 89, 87, 78, 48, 88, 50, 70, 107, 90, 72, 73, 105, 79, 105, 74, 68, 98, 50, 53, 48, 99, 109, 70, 106, 100, 67, 65, 106, 77, 105, 73, 115, 73, 109, 49, 122, 90, 121, 73, 54, 73, 109, 85, 122, 77, 68, 48, 105, 76, 67, 74, 109, 100, 87, 53, 107, 99, 121, 73, 54, 87, 49, 49, 57, 102, 81, 61, 61, 34, 44, 34, 102, 117, 110, 100, 115, 34, 58, 91, 93, 125, 125]), funds: [] }

Caused by:
    0: error executing WasmMsg:
       sender: Contract #0
       Execute { contract_addr: "Contract #1", msg: Binary([123, 34, 101, 120, 101, 99, 117, 116, 101, 34, 58, 123, 34, 99, 111, 110, 116, 114, 97, 99, 116, 95, 97, 100, 100, 114, 34, 58, 34, 67, 111, 110, 116, 114, 97, 99, 116, 32, 35, 50, 34, 44, 34, 109, 115, 103, 34, 58, 34, 101, 51, 48, 61, 34, 44, 34, 102, 117, 110, 100, 115, 34, 58, 91, 93, 125, 125]), funds: [] }
    1: error executing WasmMsg:
       sender: Contract #1
       Execute { contract_addr: "Contract #2", msg: Binary([123, 125]), funds: [] }
    2: Contract returned an error on execute msg:
       EmptyMsg
    3: Generic error: Handle failed', packages/multi-test/src/app.rs:2693:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

.unwrap_err();

// uncomment to have the test fail and see how the error stringifies
// panic!("{:?}", err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great output.
I just also realise we need a better debug encoding for Binary.

Added CosmWasm/cosmwasm#1199 which could be an interesting follow-up sometime.

msg,
let binary_msg = to_binary(msg)?;
let wrapped_msg = WasmMsg::Execute {
contract_addr: contract_addr.to_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to_string is more explicit, but does require a clone.
into_string?

(Note: this is pedantic to even worry about performance here... this is test code after all... just my usual review bugs chipped in)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Ok(Response::default())
}

fn execute(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an even simpler form of reflect. Great example

.context(format!(
"Contract returned an error on execute msg:\n{:?}",
msg,
))
}

fn instantiate(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do the same map error here and for query, right?

I assume that will be a follow-up PR, but just want to double check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, I guess I shouldn't close the issue until those are implemented.

@uint uint merged commit 11e9585 into main Jan 4, 2022
@uint uint deleted the 575-multitest-debug branch January 4, 2022 20:41
let msg = from_slice(&msg)?;
(self.instantiate_fn)(deps, env, info, msg).map_err(|err| anyhow!(err))
let msg: T2 = from_slice(&msg)?;
(self.instantiate_fn)(deps, env, info, msg.clone())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought, we could remove the Clone bound if you did the following, right?

let ctx = format!("Contract returned an error on instantiate msg:\n{:?}", msg);
(self.instantiate_fn)(deps, env, info, msg).map_err(anyhow::Error::from).context(ctx)

Nothing really important, just like reducing required bounds and removing clones when possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, yeah, that looks much better. Will do 👍

@@ -319,13 +319,13 @@ impl<T1, T2, T3, E1, E2, E3, C, T4, E4, E5, T6, E6> Contract<C>
for ContractWrapper<T1, T2, T3, E1, E2, E3, C, T4, E4, E5, T6, E6>
where
T1: DeserializeOwned + Debug + Clone,
T2: DeserializeOwned,
T3: DeserializeOwned,
T2: DeserializeOwned + Debug + Clone,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would remove Clone from T1, T2, T3

}

fn query(&self, deps: Deps, env: Env, msg: Vec<u8>) -> AnyResult<Binary> {
let msg = from_slice(&msg)?;
(self.query_fn)(deps, env, msg).map_err(|err| anyhow!(err))
let msg: T3 = from_slice(&msg)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to add these two and the simple test. Looks good to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add more debug output from multi-test
2 participants