ink: Implement error forwarding
If an ink! message returns a Result::Err, this error should be forwarded and result in e.g. the Canvas UI eventually displaying Extrinsic Failed (instead of Extrinsic Success, which it currently does).
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 58 (13 by maintainers)
You are completely right about 1. This is already implemented. The UIs just don’t make use of it right now.
Events are something that are in the domain of the individual Dapps that build on ink!. A generic UI would just show all events generated by a contract. They can be decoded automatically by using the ABI file. Please keep in mind that the target audience of those generic UIs are contract developers and not users. For developers those the decoded events make sense.
Users will interact with contracts through custom Dapps that can make sense of those events and display them in a friendly manner.
Ah I think I finally get what you mean. I will inspect it.
I only wanted to point out that if there is an metadata attribute called
revert_on_errthen that’s not very descriptive, I think. I agree that if is possible to not have a metadata attribute, then that is even nicer.I will try to describe the whole flow, how it can be implemented and used, based on the conversations above.
Let’s highlight that we have two types of errors that can be used for error forwarding/debugging:
ContractTrappedandContractReverted. I guess both of them will have a buffer for data in the future based on this comment.Based on the type of error you need to decide, how to decode it.
ContractRevertedcan be returned viareturn_valuefunction withREVERTflag. It means that the developer can return this error only in the expected execution flow. So I guess it is normal if we introduce an unspoken rule, that the developer must return only the type described in ABI viareturn_valuefunction(It means that if the method X in ABI returnsResult<.., ErrorA>, it must return onlyResult<.., ErrorA>and notResult<.., ErrorB>orString). In the case of thereturn_on_errattribute, this restriction will be implemented automatically during compilation time, because ink! will add a code to encode the result(So it passes the encoded data into the buffer ofContractReverted. For encoding ink! and thecontract-palletare using parity-scale-codec, so other applications must use the same algorithm for decoding).The developer must follow this rule because:
ContractRevertedwill be converted intoCalleeRevertederror, but it is a positive error, it only means that the child contract didn’t update the state. The parent contract must decide what to do with the result by self. If the developer broke that rule, it means that we can’t decode the data fromCalleeReverted, but ink!(And I guess other languages) will try to do it automatically based on the ABI of the child contract and it will cause aDecodeErrorwhich already can be emitted viaContractTrapped.X. It still can represent it like a string in not typed languages(or like another analog of Result with string as error), but it will be not usable and complicated.The
ContractTrappederror must be used only for unexpected behaviors. Like divide by zero,DecodeErrorand etc. The data from that error can be interpreted only like a string(maybe bytes in some cases), but no more. It is an unexpected error so we can’t unify it.Based on the above, it is clear how the
revert_on_errattribute can be implemented. If the user specifies that attribute, it means that the return type is aResult. ink! can add the code to check that if the result is an error, let’s encode(Based on the return type of the method) it and pass it intoreturn_valuewith theREVERTflag. If it is a simple call, we will propagate this result to the user. If it is a cross call, we will propagate this result intoCalleeReverted, and ink! will interpreter this error like a positive result and will try to decode the data from this error. If the decode is successful, we will return the result to the parent contract, overwise we will panic and emitContractTrapped.The developer of the contract can use
return_valuewith theREVERTflag in other cases not related toResult. But I don’t see at the moment a valid use case for that(which will be better than usage withResult)=D Maybe the use case where you can decide to revert the changes but you want to pass some data back to the user/contract… I don’t know) But I think the rule that the type of passed data intoreturn_valuemust be like in ABI is the core rule.It will revert. However, I am not sure if the planned ink! design for this feature is in a good shape yet.
In a meeting @cmichi and me concluded to put forward with the following design for ink!:
revert_on_err: boolthat is optional and which can be applied to both, ink! constructors and messages.Resulttype and furthermore ink! will revert the contract execution upon yieldingResult::Errwith appropriate return flags to SEAL.revert_on_err: Option<bool>. Optional means that it can be missing. Note that it is kind of specific to ink!/Rust and not very useful for other languages like Solang which is why it cannot be mandatory. When the field is missingcargo-contractwill assert that the associated ink! constructor or message in fact does NOT return aResult. This means that whenver a user returns aResulttype having this new ink! attribute is mandatory to set. If the user wants to revert the execution uponResult::Errthe message or constructors should be flagged with eitherrevert_on_errorrevert_on_err: truewhich is both equivalent. If the execution should not be reverted uponResult::Errthe user is mandated to flag the ink! constructor or message withrevert_on_err: false.callanddeployno longer return au32to make the ink! codegen compatible with the most recent SEAL versions again.This design allows for an efficient design that ink! and
cargo-contactcan statically verify and which pleasantly fits into the current ink! design from a syntactical point of view.An example follows: