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 --output-json for call, instantiate & upload commands #722

Merged
merged 26 commits into from
Sep 12, 2022

Conversation

SkymanOne
Copy link
Contributor

@SkymanOne SkymanOne commented Sep 1, 2022

Summary

As requested this PR closes #682, this PR adds support for printing out response from calling cargo contract instantiate .... and cargo contract call ... outputs in JSON format by introducing --output-json flag.

Previous human-readable format has been preserved.

Examples

Calling a contract with arg

cargo contract call --contract 5HLJobbpgd9EpJFySGDEanHkgh4nMaG6h1pFq9D2A5aTqibt --message inc --args 5 --suri //Alice --skip-confirm --output-json
{
  "verbosity": "Default",
  "estimated_gas": 74999922688,
  "events": [
    {
      "pallet": "Balances",
      "name": "Withdraw",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 86298156
          }
        }
      ]
    },
    {
      "pallet": "TransactionPayment",
      "name": "TransactionFeePaid",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "actual_fee",
          "value": {
            "UInt": 86298156
          }
        },
        {
          "name": "tip",
          "value": {
            "UInt": 0
          }
        }
      ]
    },
    {
      "pallet": "System",
      "name": "ExtrinsicSuccess",
      "fields": [
        {
          "name": "dispatch_info",
          "value": {
            "Map": {
              "weight": {
                "UInt": 7841193043
              },
              "class": {
                "Tuple": {
                  "ident": "Normal",
                  "values": []
                }
              },
              "pays_fee": {
                "Tuple": {
                  "ident": "Yes",
                  "values": []
                }
              }
            }
          }
        }
      ]
    }
  ]
}

Calling a contract with --dry-run flag

cargo contract call --contract 5HLJobbpgd9EpJFySGDEanHkgh4nMaG6h1pFq9D2A5aTqibt --message inc --args 5 --suri //Alice --skip-confirm --dry-run --output-json
{
  "result": "Success!",
  "reverted": false,
  "data": "Unit",
  "gas_consumed": 7391961043,
  "gas_required": 74999922688,
  "storage_deposit": {
    "charge": "0x0"
  }
}

Instantiating contract

{
  "contract": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9",
  "code_hash": "0xd73514bcae8754202a7cf32b2d4057ba9c95e7e82a3cb8f00b5ce1c64995ab70",
  "verbosity": "Default",
  "estimated_gas": 27371571412,
  "events": [
    {
      "pallet": "Balances",
      "name": "Withdraw",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 31189830465
          }
        }
      ]
    },
    {
      "pallet": "System",
      "name": "NewAccount",
      "fields": [
        {
          "name": "account",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Endowed",
      "fields": [
        {
          "name": "account",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        },
        {
          "name": "free_balance",
          "value": {
            "UInt": 100405000000
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Transfer",
      "fields": [
        {
          "name": "from",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "to",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 100405000000
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Reserved",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 100405000000
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Reserved",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 536140000000
          }
        }
      ]
    },
    {
      "pallet": "Contracts",
      "name": "CodeStored",
      "fields": [
        {
          "name": "code_hash",
          "value": {
            "Hex": {
              "s": "d73514bcae8754202a7cf32b2d4057ba9c95e7e82a3cb8f00b5ce1c64995ab70",
              "bytes": [
                215,
                53,
                20,
                188,
                174,
                135,
                84,
                32,
                42,
                124,
                243,
                43,
                45,
                64,
                87,
                186,
                156,
                149,
                231,
                232,
                42,
                60,
                184,
                240,
                11,
                92,
                225,
                198,
                73,
                149,
                171,
                112
              ]
            }
          }
        }
      ]
    },
    {
      "pallet": "Contracts",
      "name": "Instantiated",
      "fields": [
        {
          "name": "deployer",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "contract",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Transfer",
      "fields": [
        {
          "name": "from",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "to",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 100020000000
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Reserved",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 100020000000
          }
        }
      ]
    },
    {
      "pallet": "Balances",
      "name": "Deposit",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "amount",
          "value": {
            "UInt": 27233000969
          }
        }
      ]
    },
    {
      "pallet": "TransactionPayment",
      "name": "TransactionFeePaid",
      "fields": [
        {
          "name": "who",
          "value": {
            "Literal": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
          }
        },
        {
          "name": "actual_fee",
          "value": {
            "UInt": 3956829496
          }
        },
        {
          "name": "tip",
          "value": {
            "UInt": 0
          }
        }
      ]
    },
    {
      "pallet": "System",
      "name": "ExtrinsicSuccess",
      "fields": [
        {
          "name": "dispatch_info",
          "value": {
            "Map": {
              "weight": {
                "UInt": 3870509443
              },
              "class": {
                "Tuple": {
                  "ident": "Normal",
                  "values": []
                }
              },
              "pays_fee": {
                "Tuple": {
                  "ident": "Yes",
                  "values": []
                }
              }
            }
          }
        }
      ]
    }
  ]
}

Instantiating a contract with dry-run

cargo contract instantiate --suri //Alice target/ink/incrementer.wasm --skip-dry-run --gas 27371571412 --args 0 --skip-confirm --dry-run --output-json
{
  "result": "Success!",
  "contract": "5EMUDqdfmSsyE7v3uksHtGRuEeQrgk7QteoJ8tXhqGANDCfG",
  "reverted": false,
  "data": "0x",
  "gas_consumed": 7360357127,
  "gas_required": 74999922688,
  "storage_deposit": {
    "charge": "0x2eaa42cc40"
  }
}

Updates

  • Added serialised StorageDeposit changes
  • Generic serialisation of Module errors and Generic error
  • Added an entry to the Changelog

@SkymanOne
Copy link
Contributor Author

Currently, JSON is only displayed when if the operation has been successful. Should JSON response be introduced for errors?

@cmichi
Copy link
Collaborator

cmichi commented Sep 2, 2022

Currently, JSON is only displayed when if the operation has been successful. Should JSON response be introduced for errors?

That would be ideal, then third-party tooling can make better use of --output-json for scripting. I would print the errors as JSON on stderr.

For context: The PR has conflicts with master since we intentionally decided to branch it off before the breaking metadata changes, which currently prevent these commands on master from being used against a Substrate node.

@SkymanOne
Copy link
Contributor Author

SkymanOne commented Sep 2, 2022

Update

Error are now partially json-rendered. Only instantiate command support serialised error output, call command can not produce serialised errors because of Error enum from subxt crate does not have serde derive

Example

cargo contract instantiate --suri //Charlie --code-hash 0xd73514bcae8754202a7cf32b2d4057ba9c95e7e82a3cb8f00b5ce1c64995ab70 --constructor new --args 0 --output-json
{
  "pallet": "Contracts",
  "error": "DuplicateContract",
  "docs": [
    "A contract with the same AccountId already exists."
  ]
}

P.S.

After short conversation with @ascjones It was agreed that RPC error does not have to and can not be serialised properly
Here is a sample result:

cargo contract call --contract 5FUdyfmrdbk1H5F1E4HnweiQjENqwAWnPxH2Mkty6LeK5at9 --message inc --args 5 --suri //Alice_S --skip-confirm --output-json
"Rpc error: RPC call failed: ErrorObject { code: ServerError(1010), message: \"Invalid Transaction\", data: Some(RawValue(\"Inability to pay some fees (e.g. account balance too low)\")) }"

@SkymanOne SkymanOne added enhancement New feature or request blocked This task is blocked until blockers are resolved. and removed blocked This task is blocked until blockers are resolved. labels Sep 2, 2022
@SkymanOne
Copy link
Contributor Author

Update

Module and Generic errors are now serialised generically

ModuleError

{
  "module_error": {
    "pallet": "Contracts",
    "error": "DuplicateContract",
    "docs": [
      "A contract with the same AccountId already exists."
    ]
  }
}

GenericError

{
  "generic_error": {
    "error": "Rpc error: RPC call failed: ErrorObject { code: ServerError(1010), message: \"Invalid Transaction\", data: Some(RawValue(\"Inability to pay some fees (e.g. account balance too low)\")) }"
  }
}

Copy link
Collaborator

@ascjones ascjones left a comment

Choose a reason for hiding this comment

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

WIP review

crates/transcode/src/scon/mod.rs Outdated Show resolved Hide resolved
crates/cargo-contract/src/cmd/extrinsics/call.rs Outdated Show resolved Hide resolved
crates/cargo-contract/src/cmd/extrinsics/mod.rs Outdated Show resolved Hide resolved
crates/cargo-contract/src/cmd/extrinsics/mod.rs Outdated Show resolved Hide resolved
crates/cargo-contract/src/cmd/extrinsics/mod.rs Outdated Show resolved Hide resolved
crates/cargo-contract/src/cmd/extrinsics/call.rs Outdated Show resolved Hide resolved
ascjones and others added 2 commits September 7, 2022 17:57
* Convert subxt error to custom ErrorVariant

* Refactor displaying of extrinsic result events
crates/cargo-contract/src/cmd/extrinsics/upload.rs Outdated Show resolved Hide resolved
"ERROR:".bright_red().bold(),
format!("{:?}", err).bright_red()
);
if !suppress_err {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we actually want to report the error as json rather than supressing it. At the moment for some errors we will get the pretty printed json at the call site, and all others will not be displayed at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If don't do this, then the output result can be like this:

{
  "module_error": {
    "pallet": "Contracts",
    "error": "DuplicateContract",
    "docs": [
      "A contract with the same AccountId already exists."
    ]
  }
}
{
  "error": "Pre-submission dry-run failed. Use --skip-dry-run to skip this step."
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Then we will need to rethink the approach. How about instead of printing the json error directly, we bubble it back up as an error?.

So instead of eprintnln!("{}", json_error); Ok(()) you could do (Err(anyhow!("{}", json_err)). Then this would be printed correctly at the top level

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made the error propagate to the top level, all the way back to the main(), then I check if an error is an json format and print out, otherwise I wrap the message into a serializable object.

// error message can be either plain text or json string
// we need to check whether the error message is json object
let json_result =
serde_json::from_str::<serde_json::Value>(&err.to_string());
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not great to serialize to json at the error site and then deserialize again at the top level. I think we can do better with a strongly typed approach see #736

SkymanOne and others added 3 commits September 12, 2022 13:20
* Move extrinsics error to its own file, update instantiate

* Update upload to unify errors

* Update call to unify errors

* Handle json errors at the top level
@SkymanOne SkymanOne changed the title Add --output-json for call and instantiate commands Add --output-json for call, instantiate & upload commands Sep 12, 2022
Copy link
Collaborator

@ascjones ascjones left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@SkymanOne SkymanOne merged commit 8ca0b03 into master Sep 12, 2022
@SkymanOne SkymanOne deleted the gn-json-outputs branch September 12, 2022 14:10
@ascjones ascjones mentioned this pull request Sep 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add --output-json for call and instantiate commands
3 participants