JSON Structure

Scenario JSON files are intended to be human-readable. Therefore, you will find the following conventions:

File Extension

Scenario files should have the ".scen.json" extension, where "scen" stands for "scenario." The framework uses this double extension to identify tests for execution. Any other extension will be ignored. It's worth noting that there is an older, now deprecated format where test file names end with ".test.json," but you need not concern yourself with it.

Top-Level Structure

A scenario test file is essentially a collection of steps to be performed on a mock blockchain. The simplest such file appears as follows:

{
  "name": "example scenario file",
  "comment": "comments are nice",
  "steps": []
}

The top-level fields are as follows:

  • name (optional): You can provide a name for scenarios, but it doesn't affect test execution.
  • comment (optional): You can include comments, but they don't affect test execution.
  • steps: This is the core of the scenario. Running a scenario involves going through various steps, each represented as a JSON map with a step field indicating the step type.

Step Type: externalSteps

{
  "steps": [
    {
      "step": "externalSteps",
      "path": "other.json"
    }
  ]
}

This step type is useful for splitting, composing, and reusing scenario steps. It allows for scenario bifurcation, where a common part can be reused in two different tests. The only specific field here is path, indicating the relative path to a JSON file containing scenario steps. The referenced file doesn't need the ".scen.json" extension and doesn't have to be a valid scenario on its own. Imported steps will run or re-run every time they are imported, and there's no caching. Be cautious as there is no protection against cyclic imports.

Step Type: setState

{
  "steps": [
    {
      "step": "setState",
      "comment": "not much to comment here, but we can",
      "accounts": {
        "address:user_account": {
          "comment": "we can comment on individual account initializations",
          "nonce": "0",
          "balance": "123,000,000,000",
          "Kda": {
            "str:MYFUNGIBLE-0001": "400,000,000,000",
            "str:MYSFT-123456": {
              "instances": [
                {
                  "nonce": "24",
                  "balance": "1"
                },
                {
                  "nonce": "25",
                  "balance": "1",
                  "creator": "address:other_creator_address",
                  "royalties": "5000",
                  "hash": "keccak256:str:other_nft_hash",
                  "uri": ["str:www.something.com/funny.jpeg"],
                  "attributes": "str:other_attributes"
                }
              ],
              "lastNonce": "7",
              "roles": [
                "KDARoleLocalMint",
                "KDARoleLocalBurn",
                "KDARoleNFTCreate",
                "KDARoleNFTAddQuantity",
                "KDARoleNFTBurn"
              ],
              "frozen": "false"
            }
          },
          "username": "str:myusername.x",
          "storage": {},
          "code": ""
        },
        "sc:smart_contract_address": {
          "nonce": "0",
          "balance": "23,000",
          "Kda": {
            "str:MYFUNGIBLE-0001": "100,000,000,000"
          },
          "storage": {
            "str:storage-key-1": "-5",
            "str:storage-key-2|u32:4": ["u32:1", "u32:2", "u32:3"]
          },
          "code": "file:smart-contract.wasm"
        }
      },
      "newAddresses": [
        {
          "creatorAddress": "address:creator",
          "creatorNonce": "1234",
          "newAddress": "sc:future_sc"
        }
      ]
    },
    {
      "step": "setState",
      "comment": "only set block info this time",
      "previousBlockInfo": {
        "blockNonce": "222",
        "blockRound": "333",
        "blockEpoch": "444"
      },
      "currentBlockInfo": {
        "blockTimestamp": "511",
        "blockNonce": "522",
        "blockRound": "533",
        "blockEpoch": "544"
      }
    }
  ]
}

This step type initializes or reconfigures the blockchain mock during execution. At least one such step is required before any execution because all transactions need existing accounts to work with. Not all sections are required each time. These sections are:

  • comment (doesn't affect execution)
  • accounts (specify any number of accounts, user accounts, and smart contracts)
  • newAddresses (generate mock contract addresses during deployment)
  • previousBlockInfo and currentBlockInfo (set or change data provided to smart contracts via hooks to simulate the passage of time in scenarios)

Step Type: checkState

This step checks the state of the blockchain mock at a certain point. It can verify the entire state or just part of it. This step can appear anywhere, not just at the end of tests, allowing progressive verification of changes.

{
  "steps": [
    {
      "step": "checkState",
      "comment": "check that previous tx did the right thing",
      "accounts": {
        "address:user_account": {
          "comment": "we can comment on individual account initializations",
          "nonce": "0",
          "balance": "*",
          "Kda": {
            "str:MYFUNGIBLE-0001": "*",
            "str:MYSFT-123456": {
              "instances": [
                {
                  "nonce": "24",
                  "balance": "*"
                },
                {
                  "nonce": "25",
                  "balance": "1",
                  "creator": "address:other_creator_address",
                  "royalties": "5000",
                  "hash": "keccak256:str:other_nft_hash",
                  "uri": ["str:www.something.com/funny.jpeg"],
                  "attributes": "str:other_attributes"
                }
              ],
              "lastNonce": "7",
              "roles": [
                "KDARoleLocalMint",
                "KDARoleLocalBurn",
                "KDARoleNFTCreate",
                "KDARoleNFTAddQuantity",
                "KDARoleNFTBurn"
              ],
              "frozen": "false"
            }
          },
          "username": "str:myusername.x",
          "storage": {},
          "code": ""
        },
        "sc:smart_contract_address": {
          "nonce": "0",
          "balance": "23,000",
          "Kda": {
            "str:MYFUNGIBLE-0001": "100,000,000,000"
          },

          "storage": {
            "str:storage-key-1": "-5",
            "str:storage-key-2|u32:4": "*",
            "+": ""
          },
          "code": "file:smart-contract.wasm"
        }
      }
    }
  ]
}

Fields include:

  • comment (optional, doesn't affect execution)
  • accounts (a map from account address to expected account state, allowing for optional entries to indicate that there can be other accounts in the blockchain mock not mentioned here)

Step Type: dumpState

This step simply prints the entire state of the blockchain mock to the console.

{
  "step": "dumpState",
  "comment": "print everything to console"
}

Step Type: scCall

This step simulates a transaction to an existing smart contract. It includes the following fields:

  • txId (optional): It shows up in error messages to help developers find a transaction that produced an incorrect result or failed. It is also used to generate mock transaction hashes.

  • comment (optional): Developers can provide comments or a description of the transaction, but it does not influence execution.

  • tx: Specifies the details of the transaction.

    • from: The account must exist in the blockchain mock.
    • to: The account must exist in the blockchain mock and must be a smart contract.
    • klvValue: Indicates how much KLV to transfer as part of the call. Only payable functions will accept this kind of payment.
    • kdaValue: A list of Kda tokens to transfer as part of the call. You cannot transfer both KLV and Kda simultaneously. Each transferred item has the following fields:
      • tokenIdentifier: The unique identifier of the Kda token.
      • nonce: For NFT/SFT tokens, the nonce is provided. For fungible tokens, the nonce is 0, and this field can be omitted.
      • value: The amount to transfer.
    • function: The name of the function to call in the contract.
    • arguments: A list of arguments to provide to the smart contract function.
    • gasLimit: The maximum amount of gas allowed in smart contract execution.
    • gasPrice: The cost of each unit of gas in ERD. (GasLimit * GasPrice) will be subtracted from the caller's balance before the call. It is allowed for gasPrice to be zero to simplify gas payment tracking.
  • expect (optional): Specifies the expected results of the transaction, including:

    • out: An ordered list of the function's return results.
    • status: Indicates whether the execution completed successfully. Status 0 means success. All contract errors result in status 4 ("user error").
    • message (optional): In case of an error, the contract can provide an error message. This field checks that the correct error occurred (empty in case of success).
    • logs: Logs produced by the contract, which can be checked for correctness.
      • address: The smart contract address that produced the log. It can be different from the transaction recipient if that contract calls another contract.
      • identifier: The event identifier.
      • topics: Event arguments provided by the contract.
      • data: Event data.
    • gas: The remaining gas for the transaction (gasLimit - gas consumed). Set to "*" to ignore this check.
    • refund: Some operations, like freeing up storage, give KLV back to the caller. Set to "*" to ignore this check.

Step Type: scQuery

This step simulates a VM query from outside the blockchain, similar to scCall. However, there are some differences:

  • There is no sender since it's an off-chain query, and there is no actual transaction.
  • Gas is irrelevant because no gas is consumed off-chain.
  • Changes are not persisted, making queries read-only.
  • You cannot transfer tokens in a query (neither KLV nor Kda).
  • Querying does not increase any account nonce.

The fields include:

  • txId (optional): Similar to scCall.
  • comment (optional): Similar to scCall.
  • tx: Specifies the details of the query.
    • to: The account must exist in the blockchain mock and must be a smart contract.
    • function: The name of the function to call in the contract.
    • arguments: A list of arguments to provide to the smart contract function.
  • expect (optional): Specifies the expected results of the query, including:
    • out: An ordered list of the function's return results.
    • status: Indicates whether the execution completed successfully (status 0 means success, while contract errors result in status 4, "user error").

Step Type: scDeploy

This step simulates the deployment of new smart contracts, similar to scCall, but with some differences. It is used specifically for deploying new smart contracts. The fields include:

  • txId (optional): Similar to scCall.

  • comment (optional): Similar to scCall.

  • tx: Specifies the details of the deployment.

    • from: The deploying account must exist in the blockchain mock.
    • value: The amount of KLV to transfer with the deployment.
    • contractCode: The code for the new contract, typically in the "file:<relative path to contract binary>" format.
    • arguments: A list of arguments to provide to the contract's initialization function.
    • gasLimit: The maximum amount of gas allowed for contract deployment.
    • gasPrice: The cost of each unit of gas in ERD. GasLimit * GasPrice will be subtracted from the caller's balance before deployment.
  • expect (optional): Specifies the expected results of the deployment, including:

    • out: An ordered list of the function's return results (empty for deployment).
    • status: Indicates whether the deployment completed successfully (status 0 means success, while

contract errors result in status 4, "user error").

  • logs: Logs produced by the contract during deployment, which can be checked for correctness.
  • gas: The consumed gas during deployment. Set to "*" to ignore this check.
  • refund: Some operations, like freeing up storage, give KLV back to the caller. Set to "0" to ignore this check.

Step Type: transfer

This lesser-used step type simulates a simple transfer of KLV between two accounts without involving the VM. Note that simple transfers are also allowed toward smart contracts. It includes the following fields:

  • txId (optional): Similar to scCall/scDeploy.
  • comment (optional): Similar to scCall/scDeploy.
  • tx: Specifies the details of the transfer.
    • from: The account from which KLV is transferred.
    • to: The account receiving the KLV.
    • klvValue: The KLV value to transfer.
    • kdaValue: A list of Kda tokens to transfer (similar to scCall).

Step Type: validatorReward

This lesser-used step type simulates a validator reward being sent by the protocol. It does not have a sender, and besides increasing the recipient's balance, it also increments the KLEVERrewards field in the smart contract storage. It includes the following fields:

  • txId (optional): Similar to scCall/scDeploy.
  • comment (optional): Similar to scCall/scDeploy.
  • tx: Specifies the details of the reward.
    • to: The smart contract receiving the reward.
    • value: The amount of KLV as a reward.

Was this page helpful?