Skip to content

[scTool] Error decoding of nested errors  #182

Description

@DOBEN

Task description

https://github.com/Concordium/concordium-smart-contract-tools/blob/main/front-end-tools/src/utils.ts#L199
Error decoding of nested errors is currently not captured correctly in the above line. An error can be nested e.g.:

{"Custom": [ {"CustomError1": []}]}

The scTool only displays the top-most error enum variant Custom in the cis2-library example.

Explanation of how nested errors are constructed in the smart contract

/// 2. Example: Deriving `Reject` in the smart contract with nested errors.
/// Nested errors are often used to inherit the errors from a smart
/// contract library such as the cis2-library.
/// ``
///
/// Library:
/// ```ignore
/// /// The custom errors the library can produce.
/// #[derive(Serialize, Debug, Reject, SchemaType)]
/// pub enum Cis2Error {
/// /// CustomErrorLibrary1 defined in smart contract logic.
/// CustomErrorLibrary1, // return_value: 00; error_code: -1
/// /// Nested error variant.
/// Custom(R),
/// // return_value: 0100; error_code: -1
/// // return_value: 0101; error_code: -2
/// // return_value: 0102; error_code: -3
/// // ...
/// /// CustomErrorLibrary2 defined in smart contract logic.
/// CustomErrorLibrary2, // return_value: 02; error_code: -3
/// }
/// ```
///
/// Smart contract:
/// ```ignore
/// /// The different errors the contract can produce.
/// #[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)]
/// pub enum CustomContractError {
/// /// CustomError1 defined in smart contract logic.
/// CustomError1, // return_value: 0100; error_code: -1
/// /// CustomError2 defined in smart contract logic.
/// CustomError2, // return_value: 0101; error_code: -2
/// /// CustomError3 defined in smart contract logic.
/// CustomError3, // return_value: 0102; error_code: -2
/// }
///
/// /// Mapping CustomContractError to ContractError
/// impl From for ContractError {
/// fn from(c: CustomContractError) -> Self { Cis2Error::Custom(c) }
/// }
///
/// pub type ContractError = Cis2Error;
///
/// pub type ContractResult = Result;
/// ```
///
/// The `Reject` macro assigns the `error_codes` starting from `-1` to the enum
/// variants and assigns the `return_values` by serializing the enum variants
/// starting with the topmost enum. The `return_values` are equivalent to the
/// enum tags of all enums in the nested chain.
///
/// The JSON value returned by this function for the above `CustomError1` is:
/// ```json
/// {\"Custom\/": Array [Object {\"CustomError1\/": Array []}]}
/// ```

A recursive approach is needed similar to below code for scTool as well to display nested errors meaningfully.

/// Write a custom display implementation for the error schema type.
/// This displays nested errors meaningfully.
impl std::fmt::Display for ErrorSchema {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.0 {
            Value::Object(map) => {
                if let Some(key) = map.keys().next() {
                    write!(f, "{}", key)?;
                    if let Some(value) = map.values().next() {
                        if value.is_array() {
                            write!(f, "{}", ErrorSchema(value.clone()))?;
                        }
                    }
                }
            }
            Value::Array(arr) => {
                if let Some(value) = arr.iter().next() {
                    write!(f, "::{}", ErrorSchema(value.clone()))?;
                }
            }
            _ => write!(f, "{}", self.0)?,
        }
        Ok(())
    }
}

Another limitation that once resolved needs to be applied to the scTool front-end is given in the comment of the PR:
Concordium/concordium-dapp-examples#88

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions