Fuel Specifications

Fuel: A Secure Decentralized Generalized Massively Scalable Transaction Ledger

This book specifies the Fuel protocol, including the Fuel Virtual Machine (short: FuelVM), a blazingly fast verifiable blockchain virtual machine.

Protocol

FuelVM

  • Overview - Describes the FuelVM at a high level, from its architecture to how it is initialized.
  • Instruction Set - Defines the FuelVM instruction set.

Network-Specific

Testing

Transaction Format

The Fuel Transaction Format.

Consensus Parameters

nametypedescription
GAS_PER_BYTEuint64Gas charged per byte of the transaction.
GAS_PRICE_FACTORuint64Unit factor for gas price.
MAX_GAS_PER_TXuint64Maximum gas per transaction.
MAX_INPUTSuint64Maximum number of inputs.
MAX_OUTPUTSuint64Maximum number of outputs.
MAX_PREDICATE_LENGTHuint64Maximum length of predicate, in instructions.
MAX_GAS_PER_PREDICATEuint64Maximum gas per predicate.
MAX_PREDICATE_DATA_LENGTHuint64Maximum length of predicate data, in bytes.
MAX_SCRIPT_LENGTHuint64Maximum length of script, in instructions.
MAX_SCRIPT_DATA_LENGTHuint64Maximum length of script data, in bytes.
MAX_MESSAGE_DATA_LENGTHuint64Maximum length of message data, in bytes.
MAX_STORAGE_SLOTSuint64Maximum number of initial storage slots.
MAX_TRANSACTION_SIZEuint64Maximum size of a transaction, in bytes.
MAX_WITNESSESuint64Maximum number of witnesses.
MAX_BYTECODE_SUBSECTIONSuint64Maximum number of bytecode subsections.
CHAIN_IDuint64A unique per-chain identifier.
BASE_ASSET_IDbytes32The base asset of the chain.
PRIVILEGED_ADDRESSbytes32The privileged address of the network who can perform upgrade.

Transaction

enum TransactionType : uint8 {
    Script = 0,
    Create = 1,
    Mint = 2,
    Upgrade = 3,
    Upload = 4,
}
nametypedescription
typeTransactionTypeTransaction type.
dataOne of TransactionScript, TransactionCreate, TransactionMint, TransactionUpgrade, or TransactionUploadTransaction data.

Given helper max_gas() returns the maximum gas that the transaction can use. Given helper count_ones() that returns the number of ones in the binary representation of a field. Given helper count_variants() that returns the number of variants in an enum. Given helper sum_variants() that sums all variants of an enum.

Transaction is invalid if:

  • type is not valid TransactionType value
  • inputsCount > MAX_INPUTS
  • outputsCount > MAX_OUTPUTS
  • witnessesCount > MAX_WITNESSES
  • size > MAX_TRANSACTION_SIZE. The size of a transaction is calculated as the sum of the sizes of its static and dynamic parts, as determined by canonical serialization.
  • No inputs are of type InputType.Coin or InputType.Message with input.dataLength == 0
  • More than one output is of type OutputType.Change for any asset ID in the input set
  • More than one output is of type OutputType.Change with identical asset_id fields.
  • Any output is of type OutputType.Change for any asset ID not in the input set
  • More than one input of type InputType.Coin for any Coin ID in the input set
  • More than one input of type InputType.Contract for any Contract ID in the input set
  • More than one input of type InputType.Message for any Message ID in the input set
  • if type != TransactionType.Mint
    • max_gas(tx) > MAX_GAS_PER_TX
    • No policy of type PolicyType.MaxFee is set
    • count_ones(policyTypes) > count_variants(PolicyType)
    • policyTypes > sum_variants(PolicyType)
    • len(policies) > count_ones(policyTypes)

When serializing a transaction, fields are serialized as follows (with inner structs serialized recursively):

  1. uint8, uint16, uint32, uint64: big-endian right-aligned to 8 bytes.
  2. byte[32]: as-is.
  3. byte[]: as-is, with padding zeroes aligned to 8 bytes.

When deserializing a transaction, the reverse is done. If there are insufficient bytes or too many bytes, the transaction is invalid.

TransactionScript

enum ReceiptType : uint8 {
    Call = 0,
    Return = 1,
    ReturnData = 2,
    Panic = 3,
    Revert = 4,
    Log = 5,
    LogData = 6,
    Transfer = 7,
    TransferOut = 8,
    ScriptResult = 9,
    MessageOut = 10,
    Mint = 11,
    Burn = 12,
}
nametypedescription
scriptGasLimituint64Gas limits the script execution.
receiptsRootbyte[32]Merkle root of receipts.
scriptLengthuint64Script length, in instructions.
scriptDataLengthuint64Length of script input data, in bytes.
policyTypesuint32Bitfield of used policy types.
inputsCountuint16Number of inputs.
outputsCountuint16Number of outputs.
witnessesCountuint16Number of witnesses.
scriptbyte[]Script to execute.
scriptDatabyte[]Script input data (parameters).
policiesPolicy[]List of policies, sorted by PolicyType.
inputsInput[]List of inputs.
outputsOutput[]List of outputs.
witnessesWitness[]List of witnesses.

Given helper len() that returns the number of bytes of a field.

Transaction is invalid if:

  • Any output is of type OutputType.ContractCreated
  • scriptLength > MAX_SCRIPT_LENGTH
  • scriptDataLength > MAX_SCRIPT_DATA_LENGTH
  • scriptLength * 4 != len(script)
  • scriptDataLength != len(scriptData)

Note: when signing a transaction, receiptsRoot is set to zero.

Note: when verifying a predicate or executing a script, receiptsRoot is initialized to zero.

The receipts root receiptsRoot is the root of the binary Merkle tree of receipts. If there are no receipts, its value is set to the root of the empty tree, i.e. 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

TransactionCreate

nametypedescription
bytecodeWitnessIndexuint16Witness index of contract bytecode to create.
saltbyte[32]Salt.
storageSlotsCountuint64Number of storage slots to initialize.
policyTypesuint32Bitfield of used policy types.
inputsCountuint16Number of inputs.
outputsCountuint16Number of outputs.
witnessesCountuint16Number of witnesses.
storageSlots(byte[32], byte[32]])[]List of storage slots to initialize (key, value).
policiesPolicy[]List of policies.
inputsInput[]List of inputs.
outputsOutput[]List of outputs.
witnessesWitness[]List of witnesses.

Transaction is invalid if:

  • Any input is of type InputType.Contract or InputType.Message where input.dataLength > 0
  • Any input uses non-base asset.
  • Any output is of type OutputType.Contract or OutputType.Variable or OutputType.Message
  • Any output is of type OutputType.Change with non-base asset_id
  • It does not have exactly one output of type OutputType.ContractCreated
  • tx.data.witnesses[bytecodeWitnessIndex].dataLength > CONTRACT_MAX_SIZE
  • bytecodeWitnessIndex >= tx.witnessesCount
  • The keys of storageSlots are not in ascending lexicographic order
  • The computed contract ID (see below) is not equal to the contractID of the one OutputType.ContractCreated output
  • storageSlotsCount > MAX_STORAGE_SLOTS
  • The Sparse Merkle tree root of storageSlots is not equal to the stateRoot of the one OutputType.ContractCreated output

Creates a contract with contract ID as computed here.

TransactionMint

The transaction is created by the block producer and is not signed. Since it is not usable outside of block creation or execution, all fields must be fully set upon creation without any zeroing. This means that the transaction ID must also include the correct txPointer value, not zeroed out.

nametypedescription
txPointerTXPointerThe location of the Mint transaction in the block.
inputContractInputContractThe contract UTXO that assets are minted to.
outputContractOutputContractThe contract UTXO that assets are being minted to.
mintAmountuint64The amount of funds minted.
mintAssetIdbyte[32]The asset IDs corresponding to the minted amount.
gasPriceuint64The gas price to be used in calculating all fees for transactions on block

Transaction is invalid if:

  • txPointer is zero or doesn't match the block.
  • outputContract.inputIndex is not zero

TransactionUpgrade

The Upgrade transaction allows upgrading either consensus parameters or state transition function used by the network to produce future blocks. The Upgrade Purpose type defines the purpose of the upgrade.

The Upgrade transaction is chargeable, and the sender should pay for it. Transaction inputs should contain only base assets.

Only the privileged address from ConsensusParameters can upgrade the network. The privileged address can be either a real account or a predicate.

When the upgrade type is UpgradePurposeType.ConsensusParameters serialized consensus parameters are available in the witnesses and the Upgrade transaction is self-contained because it has all the required information.

When the upgrade type is UpgradePurposeType.StateTransition, the bytecodeRoot field contains the Merkle root of the new bytecode of the state transition function. The bytecode should already be available on the blockchain at the upgrade point; otherwise, the upgrade will fail. The bytecode can be part of the genesis block or can be uploaded via the TransactionUpload transaction.

The block header contains information about which versions of consensus parameters and state transition function are used to produce a block, and the Upgrade transaction defines behavior corresponding to the version. When the block executes the Upgrade transaction, it defines new behavior for either BlockHeader.consensusParametersVersion + 1 or BlockHeader.stateTransitionBytecodeVersion + 1(it depends on the purpose of the upgrade).

When the Upgrade transaction is included in the block, it doesn't affect the current block execution. Since behavior is now defined, the inclusion of the Upgrade transaction allows the production of the next block with a new version. The block producer can still continue to use the previous version and start using a new version later unless the state transition function forbids it.

The behavior is set once per version. It is forbidden to override the behavior of the network. Each behavior should have its own version. The version should grow monotonically without jumps.

The BlockHeader.consensusParametersVersion and BlockHeader.stateTransitionBytecodeVersion are independent and can grow at different speeds.

nametypedescription
upgradePurposeUpgradePurposeThe purpose of the upgrade.
policyTypesuint32Bitfield of used policy types.
inputsCountuint16Number of inputs.
outputsCountuint16Number of outputs.
witnessesCountuint16Number of witnesses.
policiesPolicy[]List of policies.
inputsInput[]List of inputs.
outputsOutput[]List of outputs.
witnessesWitness[]List of witnesses.

Transaction is invalid if:

  • Any input is of type InputType.Contract or InputType.Message where input.dataLength > 0
  • Any input uses non-base asset.
  • Any output is of type OutputType.Contract or OutputType.Variable or OutputType.Message or OutputType.ContractCreated
  • Any output is of type OutputType.Change with non-base asset_id
  • No input where InputType.Message.owner == PRIVILEGED_ADDRESS or InputType.Coint.owner == PRIVILEGED_ADDRESS
  • The UpgradePurpose is invalid

TransactionUpload

The Upload transaction allows the huge bytecode to be divided into subsections and uploaded slowly to the chain. The Binary Merkle root built on top of subsections is an identifier of the bytecode.

Each transaction uploads a subsection of the code and must contain proof of connection to the root. All subsections should be uploaded sequentially, which allows the concatenation of previously uploaded subsections with new subsection. The bytecode is considered final when the last subsection is uploaded, and future Upload transactions with the same root fields should be rejected.

When the bytecode is completed it can be used to upgrade the network.

The size of each subsection can be arbitrary; the only limit is the maximum number of subsections allowed by the network. The combination of the transaction gas limit and the number of subsections limits the final maximum size of the bytecode.

nametypedescription
rootbyte[32]The root of the Merkle tree is created over the bytecode.
witnessIndexuint16The witness index of the subsection of the bytecode.
subsectionIndexuint16The index of the subsection of the bytecode.
subsectionsNumberuint16The total number of subsections on which bytecode was divided.
proofSetCountuint16Number of Merkle nodes in the proof.
policyTypesuint32Bitfield of used policy types.
inputsCountuint16Number of inputs.
outputsCountuint16Number of outputs.
witnessesCountuint16Number of witnesses.
proofSetbyte[32][]The proof set of Merkle nodes to verify the connection of the subsection to the root.
policiesPolicy[]List of policies.
inputsInput[]List of inputs.
outputsOutput[]List of outputs.
witnessesWitness[]List of witnesses.

Transaction is invalid if:

  • Any input is of type InputType.Contract or InputType.Message where input.dataLength > 0
  • Any input uses non-base asset.
  • Any output is of type OutputType.Contract or OutputType.Variable or OutputType.Message or OutputType.ContractCreated
  • Any output is of type OutputType.Change with non-base asset_id
  • witnessIndex >= tx.witnessesCount
  • subsectionIndex >= subsectionsNumber
  • subsectionsNumber > MAX_BYTECODE_SUBSECTIONS
  • The Binary Merkle tree root calculated from (witnesses[witnessIndex], subsectionIndex, subsectionsNumber, proofSet) is not equal to the root. Root calculation is affected by all fields, so modification of one of them invalidates the proof.

UpgradePurposeType

enum UpgradePurposeType : uint8 {
    ConsensusParameters = 0,
    StateTransition = 1,
}
nametypedescription
typeUpgradePurposeTypeType of upgrade purpose.
dataOne of ConsensusParameters, StateTransitionUpgrade purposes.

Transaction is invalid if:

  • type is not valid UpgradePurposeType value`

ConsensusParameters

nametypedescription
witnessIndexuint16Index of witness that contains a serialized(with postcard) consensus parameters.
checksumbyte[32]The hash of the serialized consensus parameters.

Given helper deserialize_consensus_parameters() that deserializes the consensus parameters from a witness by using postcard algorithm.

Transaction is invalid if:

  • witnessIndex >= tx.witnessesCount
  • checksum != sha256(tx.data.witnesses[witnessIndex])
  • deserialize_consensus_parameters(tx.data.witnesses[witnessIndex]) returns an error.

StateTransition

nametypedescription
bytecodeRootbyte[32]The root of the new bytecode of the state transition function.

Policy

// index using powers of 2 for efficient bitmasking
enum PolicyType : uint32 {
    Tip = 1,
    WitnessLimit = 2,
    Maturity = 4,
    MaxFee = 8,
}
nametypedescription
dataOne of Tip, WitnessLimit, or MaturityPolicy data.

Tip

nametypedescription
tipuint64Additional, optional fee in BASE_ASSET to incentivize block producer to include transaction

WitnessLimit

nametypedescription
witnessLimituint64The maximum amount of witness data allowed for the transaction

Given helper len() that returns the number of bytes of a field.

Transaction is invalid if:

  • len(tx.witnesses) > witnessLimit

Maturity

nametypedescription
maturityuint32Block until which the transaction cannot be included.

Transaction is invalid if:

  • blockheight() < maturity

MaxFee

nametypedescription
max_feeuint64Required policy to specify the maximum fee payable by this transaction using BASE_ASSET. This is used to check transactions before the actual gas_price is known.

Transaction is invalid if:

  • max_fee > sum_inputs(tx, BASE_ASSET_ID) - sum_outputs(tx, BASE_ASSET_ID)
  • max_fee < max_fee(tx, BASE_ASSET_ID, gas_price)

Input

enum InputType : uint8 {
    Coin = 0,
    Contract = 1,
    Message = 2,
}
nametypedescription
typeInputTypeType of input.
dataOne of InputCoin, InputContract, or InputMessageInput data.

Transaction is invalid if:

  • type > InputType.Message

InputCoin

nametypedescription
txIDbyte[32]Hash of transaction.
outputIndexuint16Index of transaction output.
ownerbyte[32]Owning address or predicate root.
amountuint64Amount of coins.
asset_idbyte[32]Asset ID of the coins.
txPointerTXPointerPoints to the TX whose output is being spent.
witnessIndexuint16Index of witness that authorizes spending the coin.
predicateGasUseduint64Gas used by predicate.
predicateLengthuint64Length of predicate, in instructions.
predicateDataLengthuint64Length of predicate input data, in bytes.
predicatebyte[]Predicate bytecode.
predicateDatabyte[]Predicate input data (parameters).

Given helper len() that returns the number of bytes of a field.

Transaction is invalid if:

  • witnessIndex >= tx.witnessesCount
  • predicateLength > MAX_PREDICATE_LENGTH
  • predicateDataLength > MAX_PREDICATE_DATA_LENGTH
  • If predicateLength > 0; the computed predicate root (see below) is not equal owner
  • predicateLength * 4 != len(predicate)
  • predicateDataLength != len(predicateData)
  • predicateGasUsed > MAX_GAS_PER_PREDICATE

Note: when signing a transaction, txPointer and predicateGasUsed are set to zero.

Note: when verifying and estimating a predicate or executing a script, txPointer and predicateGasUsed are initialized to zero.

The predicate root is computed here.

InputContract

nametypedescription
txIDbyte[32]Hash of transaction.
outputIndexuint16Index of transaction output.
balanceRootbyte[32]Root of amount of coins owned by contract before transaction execution.
stateRootbyte[32]State root of contract before transaction execution.
txPointerTXPointerPoints to the TX whose output is being spent.
contractIDbyte[32]Contract ID.

Transaction is invalid if:

  • there is not exactly one output of type OutputType.Contract with inputIndex equal to this input's index

Note: when signing a transaction, txID, outputIndex, balanceRoot, stateRoot, and txPointer are set to zero.

Note: when verifying a predicate or executing a script, txID, outputIndex, balanceRoot, stateRoot, and txPointer are initialized to zero.

InputMessage

nametypedescription
senderbyte[32]The address of the message sender.
recipientbyte[32]The address or predicate root of the message recipient.
amountuint64Amount of base asset coins sent with message.
noncebyte[32]The message nonce.
witnessIndexuint16Index of witness that authorizes spending the coin.
predicateGasUseduint64Gas used by predicate execution.
dataLengthuint64Length of message data, in bytes.
predicateLengthuint64Length of predicate, in instructions.
predicateDataLengthuint64Length of predicate input data, in bytes.
databyte[]The message data.
predicatebyte[]Predicate bytecode.
predicateDatabyte[]Predicate input data (parameters).

Given helper len() that returns the number of bytes of a field.

Transaction is invalid if:

  • witnessIndex >= tx.witnessesCount
  • dataLength > MAX_MESSAGE_DATA_LENGTH
  • predicateLength > MAX_PREDICATE_LENGTH
  • predicateDataLength > MAX_PREDICATE_DATA_LENGTH
  • If predicateLength > 0; the computed predicate root (see below) is not equal recipient
  • dataLength != len(data)
  • predicateLength * 4 != len(predicate)
  • predicateDataLength != len(predicateData)
  • predicateGasUsed > MAX_GAS_PER_PREDICATE

The predicate root is computed here.

Note: InputMessages with data length greater than zero are not considered spent until they are included in a transaction of type TransactionType.Script with a ScriptResult receipt where result is equal to 0 indicating a successful script exit

Note: when signing a transaction, predicateGasUsed is set to zero.

Note: when verifying and estimating a predicate, predicateGasUsed is initialized to zero.

Output

enum OutputType : uint8 {
    Coin = 0,
    Contract = 1,
    Change = 2,
    Variable = 3,
    ContractCreated = 4,
}
nametypedescription
typeOutputTypeType of output.
dataOne of OutputCoin, OutputContract, OutputChange, OutputVariable, or OutputContractCreated.Output data.

Transaction is invalid if:

  • type > OutputType.ContractCreated

OutputCoin

nametypedescription
tobyte[32]Receiving address or predicate root.
amountuint64Amount of coins to send.
asset_idbyte[32]Asset ID of coins.

OutputContract

nametypedescription
inputIndexuint16Index of input contract.
balanceRootbyte[32]Root of amount of coins owned by contract after transaction execution.
stateRootbyte[32]State root of contract after transaction execution.

Transaction is invalid if:

  • inputIndex >= tx.inputsCount
  • tx.inputs[inputIndex].type != InputType.Contract

Note: when signing a transaction, balanceRoot and stateRoot are set to zero.

Note: when verifying a predicate or executing a script, balanceRoot and stateRoot are initialized to zero.

The balance root balanceRoot is the root of the SMT of balance leaves. Each balance is a uint64, keyed by asset ID (a byte[32]).

The state root stateRoot is the root of the SMT of storage slots. Each storage slot is a byte[32], keyed by a byte[32].

OutputChange

nametypedescription
tobyte[32]Receiving address or predicate root.
amountuint64Amount of coins to send.
asset_idbyte[32]Asset ID of coins.

Transaction is invalid if:

  • any other output has type OutputType.OutputChange and asset ID asset_id (i.e. only one change output per asset ID is allowed)

Note: when signing a transaction, amount is set to zero.

Note: when verifying a predicate or executing a script, amount is initialized to zero.

This output type indicates that the output's amount may vary based on transaction execution, but is otherwise identical to a Coin output. An amount of zero after transaction execution indicates that the output is unspendable and can be pruned from the UTXO set.

OutputVariable

nametypedescription
tobyte[32]Receiving address or predicate root.
amountuint64Amount of coins to send.
asset_idbyte[32]Asset ID of coins.

Note: when signing a transaction, to, amount, and asset_id are set to zero.

Note: when verifying a predicate or executing a script, to, amount, and asset_id are initialized to zero.

This output type indicates that the output's amount and owner may vary based on transaction execution, but is otherwise identical to a Coin output. An amount of zero after transaction execution indicates that the output is unspendable and can be pruned from the UTXO set.

OutputContractCreated

nametypedescription
contractIDbyte[32]Contract ID.
stateRootbyte[32]Initial state root of contract.

Witness

nametypedescription
dataLengthuint64Length of witness data, in bytes.
databyte[]Witness data.

TXPointer

The location of the transaction in the block. It can be used by UTXOs as a reference to the transaction or by the transaction itself to make it unique.

nametypedescription
blockHeightuint32Block height.
txIndexuint16Transaction index.

Identifiers

This chapter defines how to compute unique identifiers.

Asset ID

The asset ID (also called asset hash) of a asset is computed as the hash of the CONTRACT_ID and a 256-bit SUB_IDENTIFIER.

sha256(CONTRACT_ID ++ SUB_IDENTIFIER)

Contract ID

For a transaction of type TransactionType.Create, tx, the contract ID is sha256(0x4655454C ++ tx.data.salt ++ root(tx.data.witnesses[bytecodeWitnessIndex].data) ++ root_smt(tx.storageSlots)), where root is the Merkle root of the binary Merkle tree with each leaf being 16KiB of instructions, and root_smt is the Sparse Merkle tree root of the provided key-value pairs. If the bytecode is not a multiple of 16 KiB, the final leaf should be zero-padded rounding up to the nearest multiple of 8 bytes.

Predicate ID

For an input of type InputType.Coin or InputType.Message, input, the predicate owner is calculated as: sha256(0x4655454C ++ root(input.predicate)), where root is the Merkle root of the binary Merkle tree each leaf being 16KiB of instructions. If the bytecode is not a multiple of 16 KiB, the final leaf should be zero-padded rounding up to the nearest multiple of 8 bytes.

Transaction ID

The transaction ID (also called transaction hash) of a transaction is computed as the hash of CHAIN_ID and the serialized transaction with fields zeroed out for signing (see different inputs and outputs for which fields are set to zero), and without witness data. In other words, only all non-witness data is hashed.

sha256(CHAIN_ID ++ serialized_tx(tx))

UTXO ID

Coin ID

Is represented as an outpoint: a pair of transaction ID as byte[32] and output index as a uint16.

Message ID

The ID of a message is computed as the hash of:

  1. the sender address as byte[32],
  2. the recipient address as byte[32],
  3. the Message nonce as byte[32],
  4. the amount being sent with the message as uint64,
  5. the message data as byte[]

hash(byte[32] ++ byte[32] ++ byte[32] ++ uint64 ++ byte[]). The address values are serialized as a byte array of length 32 left-padded with zeroes, and all other value types are serialized according to the standard transaction serialization. Note that the message data length is not included since there is only one dynamically sized field and can be implicitly determined by the hash preimage size.

Message Nonce

The nonce value for InputMessage is determined by the sending system and is published at the time the message is sent. The nonce value for OutputMessage is computed as the hash of the Transaction ID that emitted the message and the index of the message receipt uint16 (with canonical encoding): hash(byte[32] ++ canonical(uint16)).

Fee ID

The UTXO ID of collected fees in a block is the block height as a 32-byte big-endian unsigned integer (i.e. the first byte of the 32-byte array is the most significant byte, and so on).

Protocol

Transaction Validity

Transaction Life Cycle

Once a transaction is seen, it goes through several stages of validation, in this order:

  1. Pre-checks
  2. Predicate verification
  3. Script execution
  4. Post-checks

Access Lists

The validity rules below assume sequential transaction validation for side effects (i.e. state changes). However, by construction, transactions with disjoint write access lists can be validated in parallel, including with overlapping read-only access lists. Transactions with overlapping write access lists must be validated and placed in blocks in topological order.

UTXOs and contracts in the read-only and write-destroy access lists must exist (i.e. have been created previously) in order for a transaction to be valid. In other words, for a unique state element ID, the write-create must precede the write-destroy.

Read-only access list:

Write-destroy access list:

Write-create access list:

Note that block proposers use the contract ID contractID for inputs and outputs of type InputType.Contract and OutputType.Contract rather than the pair of txId and outputIndex.

VM Precondition Validity Rules

This section defines VM precondition validity rules for transactions: the bare minimum required to accept an unconfirmed transaction into a mempool, and preconditions that the VM assumes to hold prior to execution. Chains of unconfirmed transactions are omitted.

For a transaction tx, UTXO set state, contract set contracts, and message set messages, the following checks must pass.

Note: InputMessages where input.dataLength > 0 are not dropped from the messages message set until they are included in a transaction of type TransactionType.Script with a ScriptResult receipt where result is equal to 0 indicating a successful script exit.

Base Sanity Checks

Base sanity checks are defined in the transaction format.

Spending UTXOs and Created Contracts

for input in tx.inputs:
    if input.type == InputType.Contract:
        if not input.contractID in contracts:
                return False
    elif input.type == InputType.Message:
        if not input.nonce in messages:
                return False
    else:
        if not (input.txId, input.outputIndex) in state:
            return False
return True

If this check passes, the UTXO ID (txId, outputIndex) fields of each contract input is set to the UTXO ID of the respective contract. The txPointer of each input is also set to the TX pointer of the UTXO with ID utxoID.

Sufficient Balance

For each asset ID assetId in the input and output set:

def gas_to_fee(gas, gasPrice) -> int:
    """
    Converts gas units into a fee amount
    """
    return ceil(gas * gasPrice / GAS_PRICE_FACTOR)


def sum_data_messages(tx, assetId) -> int:
    """
    Returns the total balance available from messages containing data
    """
    total: int = 0
    if assetId == 0:
        for input in tx.inputs:
            if input.type == InputType.Message and input.dataLength > 0:
                total += input.amount
    return total


def sum_inputs(tx, assetId) -> int:
    total: int = 0
    for input in tx.inputs:
        if input.type == InputType.Coin and input.assetId == assetId:
            total += input.amount
        elif input.type == InputType.Message and assetId == 0 and input.dataLength == 0:
            total += input.amount
    return total


def transaction_size_gas_fees(tx) -> int:
    """
    Computes the intrinsic gas cost of a transaction based on size in bytes
    """
    return size(tx) * GAS_PER_BYTE


def minted(tx, assetId) -> int:
    """
    Returns any minted amounts by the transaction
    """
    if tx.type != TransactionType.Mint or assetId != tx.mintAssetId:
        return 0
    return tx.mint_amount


def sum_outputs(tx, assetId) -> int:
    total: int = 0
    for output in tx.outputs:
        if output.type == OutputType.Coin and output.assetId == assetId:
            total += output.amount
    return total


def input_gas_fees(tx) -> int:
    """
    Computes the intrinsic gas cost of verifying input utxos
    """
    total: int = 0
    witnessIndices = set()
    for input in tx.inputs:
        if input.type == InputType.Coin or input.type == InputType.Message:
            # add fees allocated for predicate execution
            if input.predicateLength == 0:
                # notate witness index if input is signed
                witnessIndices.add(input.witnessIndex)
            else:
                # add intrinsic gas cost of predicate merkleization based on number of predicate bytes
                total += contract_code_root_gas_fee(input.predicateLength)
                total += input.predicateGasUsed
                # add intrinsic cost of vm initialization
                total += vm_initialization_gas_fee()
    # add intrinsic cost of verifying witness signatures
    total += len(witnessIndices) * eck1_recover_gas_fee()
    return total


def metadata_gas_fees(tx) -> int:
    """
    Computes the intrinsic gas cost of processing transaction outputs
    
    The `contract_code_root_gas_fee`, `sha256_gas_fee`, and `contract_state_root_gas_fee` 
    are based on the benchmarked gas costs of these operations.
    
    Consensus parameters contain definitions of gas costs for all operations and opcodes in the network.
    """
    total: int = 0
    if tx.type == TransactionType.Create:
        for output in tx.outputs:
            if output.type == OutputType.OutputContractCreated:
                # add intrinsic cost of calculating the code root based on the size of the contract bytecode
                total += contract_code_root_gas_fee(tx.witnesses[tx.bytecodeWitnessIndex].dataLength)
                # add intrinsic cost of calculating the state root based on the number of sotrage slots
                total += contract_state_root_gas_fee(tx.storageSlotCount)
                # add intrinsic cost of calculating the contract id 
                # size = 4 byte seed + 32 byte salt + 32 byte code root + 32 byte state root
                total += sha256_gas_fee(100)
    elif tx.type == TransactionType.Upgrade:
        if tx.upgradePurpose.type == UpgradePurposeType.ConsensusParameters:
            # add intrinsic cost of calculating the consensus parameters hash
            total += sha256_gas_fee(size(tx.witnesses[tx.upgradePurpose.witnessIndex].data))
    elif tx.type == TransactionType.Upload:
        # add intrinsic cost of calculating the root based on the number of bytecode subsections
        total += contract_state_root_gas_fee(tx.subsectionsNumber)
        # add intrinsic cost of hashing the subsection for verification of the connection with Binary Merkle tree root
        total += sha256_gas_fee(size(tx.witnesses[tx.witnessIndex]))
            
    if tx.type != TransactionType.Mint:
        # add intrinsic cost of calculating the transaction id
        total += sha256_gas_fee(size(tx))
    return total


def intrinsic_gas_fees(tx) -> int:
    """
    Computes intrinsic costs for a transaction
    """
    fees: int = 0
    # add the cost of initializing a vm for the script
    if tx.type == TransactionType.Create or tx.type == TransactionType.Script:
        fees += vm_initialization_gas_fee()
        fees += metadata_gas_fees(tx)
        fees += intrinsic_input_gas_fees(tx)
    return fees


def min_gas(tx) -> int:
    """
    Comutes the minimum amount of gas required for a transaction to begin processing.
    """
    gas = transaction_size_gas_fees(tx) + intrinsic_gas_fees(tx)
    if tx.type == TransactionType.Upload
        # charge additionally for storing bytecode on chain
        gas += transaction_size_gas_fees(size(tx.witnesses[tx.witnessIndex]))
        
    return gas


def max_gas(tx) -> int:
    """
    Computes the amount of gas required to process a transaction.
    """
    gas = min_gas(tx)
    gas = gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE
    if tx.type == TransactionType.Script:
       gas += tx.gasLimit
    return gas
    
    
def maxFee(tx, assetId, gasPrice) -> int:
    """
    Computes the maximum potential amount of fees that may need to be charged to process a transaction.
    """
    maxGas = max_gas(tx)
    feeBalance = gas_to_fee(maxGas, gasPrice)
    # Only base asset can be used to pay for gas
    if assetId == 0:
        return feeBalance
    else:
        return 0


def available_balance(tx, assetId) -> int:
    """
    Make the data message balance available to the script
    """
    availableBalance = sum_inputs(tx, assetId) + sum_data_messages(tx, assetId) + minted(tx, assetId)
    return availableBalance


def unavailable_balance(tx, assetId) -> int:
    sentBalance = sum_outputs(tx, assetId)
    # Total fee balance
    feeBalance = tx.policies.max_fee
    # Only base asset can be used to pay for gas
    if assetId == 0:
        return sentBalance + feeBalance
    return sentBalance


# The sum_data_messages total is not included in the unavailable_balance since it is spendable as long as there 
# is enough base asset amount to cover gas costs without using data messages. Messages containing data can't
# cover gas costs since they are retryable.
return available_balance(tx, assetId) >= (unavailable_balance(tx, assetId) + sum_data_messages(tx, assetId))

Valid Signatures

def address_from(pubkey: bytes) -> bytes:
    return sha256(pubkey)[0:32]

for input in tx.inputs:
    if (input.type == InputType.Coin or input.type == InputType.Message) and input.predicateLength == 0:
        # ECDSA signatures must be 64 bytes
        if tx.witnesses[input.witnessIndex].dataLength != 64:
            return False
        # Signature must be from owner
        if address_from(ecrecover_k1(txhash(), tx.witnesses[input.witnessIndex].data)) != input.owner:
            return False
return True

Signatures and signature verification are specified here.

The transaction hash is computed as defined here.

Predicate Verification

For each input of type InputType.Coin or InputType.Message, and predicateLength > 0, verify its predicate.

Script Execution

Given transaction tx, the following checks must pass:

If tx.scriptLength == 0, there is no script and the transaction defines a simple balance transfer, so no further checks are required.

If tx.scriptLength > 0, the script must be executed. For each asset ID assetId in the input set, the free balance available to be moved around by the script and called contracts is freeBalance[assetId]. The initial message balance available to be moved around by the script and called contracts is messageBalance:

freeBalance[assetId] = available_balance(tx, assetId) - unavailable_balance(tx, assetId)
messageBalance = sum_data_messages(tx, 0)

Once the free balances are computed, the script is executed. After execution, the following is extracted:

  1. The transaction in-memory on VM termination is used as the final transaction which is included in the block.
  2. The unspent free balance unspentBalance for each asset ID.
  3. The unspent gas unspentGas from the $ggas register.

size(tx) encompasses the entire transaction serialized according to the transaction format, including witness data. This ensures every byte of block space either on Fuel or corresponding DA layer can be accounted for.

If the transaction as included in a block does not match this final transaction, the block is invalid.

Fees

The cost of a transaction can be described by:

def cost(tx, gasPrice) -> int:
    return gas_to_fee(min_gas(tx) + tx.gasLimit - unspentGas, gasPrice)

where:

  • min_gas(tx) is the minimum cost of the transaction in gas, including intrinsic gas fees incurred from:
    • The number of bytes comprising the transaction
    • Processing inputs, including predicates
    • Processing outputs
    • VM initialization
  • unspentGas is the amount gas left over after intrinsic fees and execution of the transaction, extracted from the $ggas register. Converting unspent gas to a fee describes how much "change" is left over from the user's payment; the block producer collects this unspent gas as reward.
  • gas_to_fee is a function that converts gas to a concrete fee based on a given gas price.

Fees incurred by transaction processing outside the context of execution are collectively referred to as intrinsic fees. Intrinsic fees include the cost of storing the transaction, calculated on a per-byte basis, the cost of processing inputs and outputs, including predicates and signature verification, and initialization of the VM prior to any predicate or script execution. Because intrinsic fees are independent of execution, they can be determined a priori and represent the bare minimum cost of the transaction.

A naturally occurring result of a variable gas limit is the concept of minimum and maximum fees. The minimum fee is, thus, the exact fee required to pay the fee balance, while the maximum fee is the minimum fee plus the gas limit:

min_gas = min_gas(tx)
max_gas = min_gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE + tx.gasLimit
min_fee = gas_to_fee(min_gas, gasPrice)
max_fee = gas_to_fee(max_gas, gasPrice)

The cost of the transaction cost(tx) must lie within the range defined by [min_fee, max_fee]. min_gas is defined as the sum of all intrinsic costs of the transaction known prior to execution. The definition of max_gas illustrates that the delta between minimum gas and maximum gas is the sum of:

  • The remaining allocation of witness bytes, converted to gas
  • The user-defined tx.gasLimit

Note that gasLimit applies to transactions of type Script. gasLimit is not applicable for transactions of type Create and is defined to equal 0 in the above formula.

A transaction cost cost(tx), in gas, greater than max_gas is invalid and must be rejected; this signifies that the user must provide a higher gas limit for the given transaction. min_fee is the minimum reward the producer is guaranteed to collect, and max_fee is the maximum reward the producer is potentially eligible to collect. In practice, the user is always charged intrinsic fees; thus, unspentGas is the remainder of max_gas after intrinsic fees and the variable cost of execution. Calculating a conversion from unspentGas to an unspent fee describes the reward the producer will collect in addition to min_fee.

VM Postcondition Validity Rules

This section defines VM postcondition validity rules for transactions: the requirements for a transaction to be valid after it has been executed.

Given transaction tx, state state, and contract set contracts, the following checks must pass.

Correct Change

If change outputs are present, they must have:

  • if the transaction does not revert;
    • if the asset ID is 0; an amount of unspentBalance + floor((unspentGas * gasPrice) / GAS_PRICE_FACTOR)
    • otherwise; an amount of the unspent free balance for that asset ID after VM execution is complete
  • if the transaction reverts;
    • if the asset ID is 0; an amount of the initial free balance plus (unspentGas * gasPrice) - messageBalance
    • otherwise; an amount of the initial free balance for that asset ID.

State Changes

Transaction processing is completed by removing spent UTXOs from the state and adding created UTXOs to the state.

Coinbase Transaction

The coinbase transaction is a mechanism for block creators to collect transaction fees.

In order for a coinbase transaction to be valid:

  1. It must be a Mint transaction.
  2. The coinbase transaction must be the last transaction within a block, even if there are no other transactions in the block and the fee is zero.
  3. The mintAmount doesn't exceed the total amount of fees processed from all other transactions within the same block.
  4. The mintAssetId matches the assetId that fees are paid in (assetId == 0).

The minted amount of the coinbase transaction intrinsically increases the balance corresponding to the inputContract. This means the balance of mintAssetId is directly increased by mintAmount on the input contract, without requiring any VM execution. Compared to coin outputs, intrinsically increasing a contract balance to collect coinbase amounts prevents the accumulation of dust during low-usage periods.

Cryptographic Primitives

Hashing

All hashing is done with SHA-2-256 (also known as SHA-256), defined in FIPS 180-4.

HashDigest

Output of the hashing function. Exactly 256 bits (32 bytes) long.

Merkle Trees

Two Merkle tree structures are used: a Binary Merkle Tree (to commit to bytecode) and a Sparse Merkle Tree (to commit to contract storage, i.e. state).

Binary Merkle Tree

Binary Merkle trees are constructed in the same fashion as described in Certificate Transparency (RFC-6962), except for using a different hashing function. Leaves are hashed once to get leaf node values and internal node values are the hash of the concatenation of their children (either leaf nodes or other internal nodes).

Nodes contain a single field:

nametypedescription
vHashDigestNode value.

The base case (an empty tree) is defined as the hash of the empty string:

node.v = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

For leaf node node of leaf data d:

node.v = h(0x00, serialize(d))

For internal node node with children l and r:

node.v = h(0x01, l.v, r.v)

Note that rather than duplicating the last node if there are an odd number of nodes (the Bitcoin design), trees are allowed to be imbalanced. In other words, the height of each leaf may be different. For an example, see Section 2.1.3 of Certificate Transparency (RFC-6962).

Leaves and internal nodes are hashed differently: the one-byte 0x00 is prepended for leaf nodes while 0x01 is prepended for internal nodes. This avoids a second-preimage attack where internal nodes are presented as leaves trees with leaves at different heights.

Binary Merkle Tree Inclusion Proofs

nametypedescription
rootHashDigest[]The expected root of the Merkle tree.
dataBytesThe data of the leaf (unhashed).
siblingsHashDigest[]Sibling hash values, ordered starting from the leaf's neighbor.

A proof for a leaf in a binary Merkle tree, as per Section 2.1.1 of Certificate Transparency (RFC-6962).

In some contexts, the array of sibling hashes is also known as the proof set. Note that proof format prescribes that leaf data be in its original, unhashed state, while the proof set (array of sibling data) uses hashed data. This format precludes the proof set from itself including the leaf data from the leaf undergoing the proof; rather, proof verification explicitly requires hashing the leaf data during the calculation of the proof set root.

Sparse Merkle Tree

Sparse Merkle Trees (SMTs) are sparse, i.e. they contain mostly empty leaves. They can be used as key-value stores for arbitrary data, as each leaf is keyed by its index in the tree. Storage efficiency is achieved through clever use of implicit defaults, avoiding the need to store empty leaves.

Additional rules are added on top of plain binary Merkle trees:

  1. Default values are given to leaf nodes with empty leaves.
  2. While the above rule is sufficient to pre-compute the values of intermediate nodes that are roots of empty subtrees, a further simplification is to extend this default value to all nodes that are roots of empty subtrees. The 32-byte zero, i.e. 0x0000000000000000000000000000000000000000000000000000000000000000, is used as the default value. This rule takes precedence over the above one.
  3. The number of hashing operations can be reduced to be logarithmic in the number of non-empty leaves on average, assuming a uniform distribution of non-empty leaf keys. An internal node that is the root of a subtree that contains exactly one non-empty leaf is replaced by that leaf's leaf node.

Nodes contain a single field:

nametypedescription
vHashDigestNode value.

In the base case, where a sparse Merkle tree has height = 0, the root of a tree is defined as the hash of the empty string:

node.v = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

When a sparse Merkle tree has a height of 0, it can have no leaves, and, therefore, no default value children. The root is then calculated as the hash of the empty string, similar to that of an empty binary Merkle tree.

For a tree with height > 0, the root of an empty tree is defined as the default value:

node.v = 0x0000000000000000000000000000000000000000000000000000000000000000

Note that this is in contrast to the base case of the sparse and binary Merkle trees, where the root is the hash of the empty string. When a sparse Merkle tree has a height greater than 0, a new tree instance is composed of default value leaves. Nodes containing only default value children have the default value as well. Applying these rules recursively percolates the default value up to the tree's root.

For leaf node node of leaf data d with key k:

node.v = h(0x00, k, h(serialize(d)))

The key of leaf nodes must be prepended, since the index of a leaf node that is not at maximum depth cannot be determined without this information. Leaf values are hashed so that they do not need to be included in full in non-membership proofs.

For internal node node with children l and r:

node.v = h(0x01, l.v, r.v)

Insertion

Before insertion of the key-value pair, each key of the Sparse Merkle Tree should be hashed with sha256 to prevent tree structure manipulations. During the proof verification, the original leaf key should be hashed similarly. Otherwise, the root will not match.

Sparse Merkle Tree Inclusion Proofs

SMTs can further be extended with compact proofs. Merkle proofs are composed, among other things, of a list of sibling node values. We note that, since nodes that are roots of empty subtrees have known values (the default value), these values do not need to be provided explicitly; it is sufficient to simply identify which siblings in the Merkle branch are roots of empty subtrees, which can be done with one bit per sibling.

For a Merkle branch of height h, an h-bit value is appended to the proof. The lowest bit corresponds to the sibling of the leaf node, and each higher bit corresponds to the next parent. A value of 1 indicates that the next value in the list of values provided explicitly in the proof should be used, and a value of 0 indicates that the default value should be used.

A proof into an SMT is structured as:

nametypedescription
depthuint16Depth of the leaf node. The root node is at depth 0. Must be <= 256.
siblingsHashDigest[]Sibling hash values, ordered starting from the leaf's neighbor.
includedSiblingsbyte[32]Bitfield of explicitly included sibling hashes.

The includedSiblings is ordered by most-significant-byte first, with each byte ordered by most-significant-bit first. The lowest bit corresponds to the leaf node level.

A specification describing a suite of test vectors and outputs of a Sparse Merkle Tree is here.

ECDSA Public-Key Cryptography

Consensus-critical data is authenticated using ECDSA, with the curve secp256k1. A highly-optimized library is available in C, with wrappers in Go and Rust.

Public keys are encoded in uncompressed form, as the concatenation of the x and y values. No prefix is needed to distinguish between encoding schemes as this is the only encoding supported.

Deterministic signatures (RFC-6979) should be used when signing, but this is not enforced at the protocol level as it cannot be.

Signatures are represented as the r and s (each 32 bytes), and v (1-bit) values of the signature. r and s take on their usual meaning (see: SEC 1, 4.1.3 Signing Operation), while v is used for recovering the public key from a signature more quickly (see: SEC 1, 4.1.6 Public Key Recovery Operation). Only low-s values in signatures are valid (i.e. s <= secp256k1.n//2); s can be replaced with -s mod secp256k1.n during the signing process if it is high. Given this, the first bit of s will always be 0, and can be used to store the 1-bit v value.

v represents the parity of the Y component of the point, 0 for even and 1 for odd. The X component of the point is assumed to always be low, since the possibility of it being high is negligible.

Putting it all together, the encoding for signatures is:

|    32 bytes   ||           32 bytes           |
[256-bit r value][1-bit v value][255-bit s value]

This encoding scheme is derived from EIP 2098: Compact Signature Representation.

EdDSA Public-Key Cryptography

Ed25519 is supported for use by applications built on Fuel. Edwards curve operations are performed by the ed25519-dalek Rust library.

Public keys are encoded in compressed form as specified by the Ed25519 format RFC-8032 5.1.5. Point compression is performed by replacing the most significant bit in the final octet of the y coordinate with the sign bit from the x coordinate:

#![allow(unused)]
fn main() {
let mut pk = y;
pk ^= x.is_negative().unwrap_u8() << 7;
}

Public keys are required to be strong enough to prevent malleability, and are checked for weakness during signature verification.

Signatures are 64 bytes, represented as the concatenation of R (32 bytes) and S (32 bytes) Where R and S are defined in RFC-8032 5.1.6.

Signatures must conform to strict verification requirements to avoid malleability concerns. While this is not part of the original Ed25519 specification, it has become a growing concern especially in cryptocurrency applications.

JSON Format for Contract Storage Initializers

Contracts can request that certain storage slots are initialized to specific values. These initialized slots are represented in JSON format as an array where each element represents a storage slot and has the following properties:

  • "key": String, a 32-byte key for a given storage slot;
  • "value": String, a 32-byte value that initializes the slot;

For instance, the following is a JSON object that requests that the 3 storage slots with keys 0x11..11, 0x22..22, and 0x33..33, are respectively initialized to the values indicated.

[
  {
    "key": "0x1111111111111111111111111111111111111111111111111111111111111111",
    "value": "0x1010101010101010101010101010101010101010101010101010101010101010"
  }, 
  {
    "key": "0x2222222222222222222222222222222222222222222222222222222222222222",
    "value": "0x2020202020202020202020202020202020202020202020202020202020202020"
  },
  {
    "key": "0x3333333333333333333333333333333333333333333333333333333333333333",
    "value": "0x0303030303030303030303030303030303030303030303030303030303030303"
  },
]

Block Header

Application Header

The application header is a network-agnostic block header. Different networks may wrap the application header in a consensus header, depending on their consensus protocol.

nametypedescription
da_heightuint64Height of the data availability layer up to which (inclusive) input messages are processed.
consensusParametersVersionuint32The version of the consensus parameters used to execute this block.
stateTransitionBytecodeVersionuint32The version of the state transition bytecode used to execute this block.
txCountuint64Number of transactions in this block.
message_receipt_countuint64Number of output messages in this block.
txRootbyte[32]Merkle root of transactions in this block.
message_receipt_rootbyte[32]Merkle root of output messages messageId in this block.

Application Binary Interface (ABI)

This document describes and specifies the ABI (Application Binary Interface) of the FuelVM, the Sway programming language, and contracts written in Sway.

JSON ABI Format

The JSON of an ABI is the human-readable representation of the interface of a Sway contract.

Notation

Before describing the format of the JSON ABI, we provide some definitions that will make the JSON ABI spec easier to read.

Given the example below:

#![allow(unused)]
fn main() {
struct Foo { x: bool }
struct Bar<T> { y: T }

fn baz(input1: Foo, input2: Bar<u64>); // an ABI function
}

we define the following expressions:

  • type declaration: the declaration or definition of a type which can be generic. struct Foo { .. } and struct Bar<T> { .. } in the example above are both type declarations.
  • type application: the application or use of a type. Foo and Bar<u64> in fn baz(input1: Foo, input2: Bar<u64>); in the example above are both applications of the type declarations struct Foo { .. } and struct Bar<T> { .. } respectively.
  • type parameter: a generic parameter used in a type declaration. T in struct Bar<T> in the example above is a type parameter.
  • type argument: an application of a type parameter used in a type application. u64 in input2: Bar<u64> in the example above is a type argument.

JSON ABI Spec

The ABI of a contract is represented as a JSON object containing the following properties:

  • "types": an array describing all the type declarations used (or transitively used) in the ABI. Each type declaration is a JSON object that contains the following properties:
    • "typeId": a unique integer ID.
    • "type": a string representation of the type declaration. The section JSON ABI Format for Each Possible Type Declaration specifies the format for each possible type.
    • "components": an array of the components of a given type, if any, and null otherwise. Each component is a type application represented as a JSON object that contains the following properties:
      • "name": the name of the component.
      • "type": the type declaration ID of the type of the component.
      • "typeArguments": an array of the type arguments used when applying the type of the component, if the type is generic, and null otherwise. Each type argument is a type application represented as a JSON object that contains the following properties:
        • "type": the type declaration ID of the type of the type argument.
        • "typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, and null otherwise. The format of the elements of this array recursively follows the rules described in this section.
    • "typeParameters": an array of type IDs of the type parameters of the type, if the type is generic, and null otherwise. Each type parameter is a type declaration and is represented as described in Generic Type Parameter.
  • "functions": an array describing all the functions in the ABI. Each function is a JSON object that contains the following properties:
    • "name": the name of the function
    • "inputs": an array of objects that represents the inputs to the function (i.e. its parameters). Each input is a type application represented as a JSON object that contains the following properties:
      • "name": the name of the input.
      • "type": the type declaration ID of the type of the input.
      • "typeArguments": an array of the type arguments used when applying the type of the input, if the type is generic, and null otherwise. Each type argument is a type application represented as a JSON object that contains the following properties:
        • "type": the type declaration ID of the type of the type argument.
        • "typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, and null otherwise. The format of the elements of this array recursively follows the rules described in this section.
    • "output": an object representing the output of the function (i.e. its return value). The output is a type application, which is a JSON object that contains the following properties:
      • "type": the type declaration ID of the type of the output.
      • "typeArguments": an array of the type arguments used when applying the type of the output, if the type is generic, and null otherwise. Each type argument is a type application represented as a JSON object that contains the following properties:
        • "type": the type declaration ID of the type of the type argument.
        • "typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, and null otherwise. The format of the elements of this array recursively follows the rules described in this section.
    • "attributes": an optional array of attributes. Each attribute is explained in the dedicated section and is represented as a JSON object that contains the following properties:
      • "name": the name of the attribute.
      • "arguments": an array of attribute arguments.
  • "loggedTypes": an array describing all instances of log or logd in the contract's bytecode. Each instance is a JSON object that contains the following properties:
    • "logId": a unique integer ID. The log and logd instructions must set their $rB register to that ID.
    • "loggedType": a type application represented as a JSON object that contains the following properties:
      • "type": the type declaration ID of the type of the value being logged.
      • "typeArguments": an array of the type arguments used when applying the type of the value being logged, if the type is generic, and null otherwise. Each type argument is a type application represented as a JSON object that contains the following properties:
        • "type": the type declaration ID of the type of the type argument.
        • "typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, and null otherwise. The format of the elements of this array recursively follows the rules described in this section.
  • "messagesTypes": an array describing all instances of smo in the contract's bytecode. Each instance is a JSON object that contains the following properties:
    • "messageDataType": a type application represented as a JSON object that contains the following properties:
      • "type": the type declaration ID of the type of the message data being sent.
      • "typeArguments": an array of the type arguments used when applying the type of the message data being sent, if the type is generic, and null otherwise. Each type argument is a type application represented as a JSON object that contains the following properties:
        • "type": the type declaration ID of the type of the type argument.
        • "typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, and null otherwise. The format of the elements of this array recursively follows the rules described in this section.
  • "configurables": an array describing all configurable variables used in the contract. Each configurable variable is represented as a JSON object that contains the following properties:
    • "name": the name of the configurable variable.
    • "configurableType": a type application represented as a JSON object that contains the following properties:
      • "type": the type declaration ID of the type of the configurable variable.
      • "typeArguments": an array of the type arguments used when applying the type of the configurable variable, if the type is generic, and null otherwise. Each type argument is a type application represented as a JSON object that contains the following properties:
        • "type": the type declaration ID of the type of the type argument.
        • "typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, and null otherwise. The format of the elements of this array recursively follows the rules described in this section.
    • "offset": the specific offset within the contract's bytecode, in bytes, to the data section entry for the configurable variable.

Note: This JSON should be both human-readable and parsable by the tooling around the FuelVM and the Sway programming language. There is a detailed specification for the binary encoding backing this readable descriptor. The Function Selector Encoding section specifies the encoding for the function being selected to be executed and each of the argument types.

Attributes Semantics

Attribute nameAttribute argumentsSemantics
storageread and/or writeSpecifies if a function reads or writes to/from storage
payableNoneSpecifies if a function can accept coins: a function without payable attribute must not accept coins
testNoneSpecifies if a function is a unit test
inlinenever or always, but not bothSpecifies if a function should be inlined during code generation
doc-commentStringDocumentation comment
docNot defined yetNot defined yet

A Simple Example

Below is a simple example showing how the JSON ABI for an example that does not use generic or complex types. We will later go over more complex examples.

#![allow(unused)]
fn main() {
abi MyContract {
    fn first_function(arg: u64) -> bool;
    fn second_function(arg: b256);
}
}

the JSON representation of this ABI looks like:

{
  "types": [
    {
      "typeId": 0,
      "type": "()",
      "components": [],
      "typeParameters": null
    },
    {
      "typeId": 1,
      "type": "b256",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 2,
      "type": "bool",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 3,
      "type": "u64",
      "components": null,
      "typeParameters": null
    }
  ],
  "functions": [
    {
      "inputs": [
        {
          "name": "arg",
          "type": 3,
          "typeArguments": null
        }
      ],
      "name": "first_function",
      "output": {
        "type": 2,
        "typeArguments": null
      }
    },
    {
      "inputs": [
        {
          "name": "arg",
          "type": 1,
          "typeArguments": null
        }
      ],
      "name": "second_function",
      "output": {
        "type": 0,
        "typeArguments": null
      }
    }
  ],
  "loggedTypes": []
}

JSON ABI Format for Each Possible Type Declaration

Below is a list of the JSON ABI formats for each possible type declaration:

()

{
  "typeId": <id>,
  "type": "()",
  "components": null,
  "typeParameters": null
}

bool

{
  "typeId": <id>,
  "type": "bool",
  "components": null,
  "typeParameters": null
}

u8

{
  "typeId": <id>,
  "type": "u8",
  "components": null,
  "typeParameters": null
}

u16

{
  "typeId": <id>,
  "type": "u16",
  "components": null,
  "typeParameters": null
}

u32

{
  "typeId": <id>,
  "type": "u32",
  "components": null,
  "typeParameters": null
}

u64

{
  "typeId": <id>,
  "type": "u64",
  "components": null,
  "typeParameters": null
}

b256

{
  "typeId": <id>,
  "type": "b256",
  "components": null,
  "typeParameters": null
}

struct

{
  "typeId": <id>,
  "type": "struct <struct_name>",
  "components": [
    {
      "name": "<field1_name>",
      "type": <field1_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },
    {
      "name": "<field2_name>",
      "type": <field2_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },
    ...
  ],
  "typeParameters": [
    <type_param1_type_id>,
    <type_param2_type_id>,
    ...
  ]
}

enum

{
  "typeId": <id>,
  "type": "enum <enum_name>",
  "components": [
    {
      "name": "<variant1_name>",
      "type": <variant1_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },
    {
      "name": "<variant2_name>",
      "type": <variant2_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },
    ...
  ],
  "typeParameters": [
    <type_param1_type_id>,
    <type_param2_type_id>,
    ...
  ]
}

str[<n>]

{
  "typeId": <id>,
  "type": "str[<n>]",
  "components": null,
  "typeParameters": null
}

<n> is the length of the string.

array

{
  "typeId": <id>,
  "type": "[_; <n>]",
  "components": [
    {
      "name": "__array_element",
      "type": "<element_type>",
      "typeArguments": ...
    }
    {
      "name": "__array_element",
      "type": <element_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },

  ],
  "typeParameters": null
}
  • <n> is the size of the array.

tuple

{
  "typeId": <id>,
  "type": "(_, _, ...)",
  "components": [
    {
      "name": "__tuple_element",
      "type": <field1_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },
    {
      "name": "__tuple_element",
      "type": <field2_type_id>,
      "typeArguments": [
        {
          "type": <type_arg1_type_id>,
          "typeArguments": ...
        },
        {
          "type": <type_arg2_type_id>,
          "typeArguments": ...
        },
        ...
      ]
    },
    ...
  ],
  "typeParameters": null
}

Generic Type Parameter

{
  "typeId": <id>,
  "type": "generic <name>",
  "components": null,
  "typeParameters": null
}

<name> is the name of the generic parameter as specified in the struct or enum declaration that uses it.

Some Complex Examples

An Example with Non-Generic Custom Types

Given the following ABI declaration:

#![allow(unused)]
fn main() {
enum MyEnum {
    Foo: u64,
    Bar: bool,
}

struct MyStruct {
    bim: u64,
    bam: MyEnum,
}

abi MyContract {
    /// this is a doc comment
    #[payable, storage(read, write)]
    fn complex_function(
        arg1: ([str[5]; 3], bool, b256),
        arg2: MyStruct,
    );
}
}

its JSON representation would look like:

{
  "types": [
    {
      "typeId": 0,
      "type": "()",
      "components": [],
      "typeParameters": null
    },
    {
      "typeId": 1,
      "type": "(_, _, _)",
      "components": [
        {
          "name": "__tuple_element",
          "type": 2,
          "typeArguments": null
        },
        {
          "name": "__tuple_element",
          "type": 4,
          "typeArguments": null
        },
        {
          "name": "__tuple_element",
          "type": 3,
          "typeArguments": null
        }
      ],
      "typeParameters": null
    },
    {
      "typeId": 2,
      "type": "[_; 3]",
      "components": [
        {
          "name": "__array_element",
          "type": 6,
          "typeArguments": null
        }
      ],
      "typeParameters": null
    },
    {
      "typeId": 3,
      "type": "b256",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 4,
      "type": "bool",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 5,
      "type": "enum MyEnum",
      "components": [
        {
          "name": "Foo",
          "type": 8,
          "typeArguments": null
        },
        {
          "name": "Bar",
          "type": 4,
          "typeArguments": null
        }
      ],
      "typeParameters": null
    },
    {
      "typeId": 6,
      "type": "str[5]",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 7,
      "type": "struct MyStruct",
      "components": [
        {
          "name": "bim",
          "type": 8,
          "typeArguments": null
        },
        {
          "name": "bam",
          "type": 5,
          "typeArguments": null
        }
      ],
      "typeParameters": null
    },
    {
      "typeId": 8,
      "type": "u64",
      "components": null,
      "typeParameters": null
    }
  ],
  "functions": [
    {
      "inputs": [
        {
          "name": "arg1",
          "type": 1,
          "typeArguments": null
        },
        {
          "name": "arg2",
          "type": 7,
          "typeArguments": null
        }
      ],
      "name": "complex_function",
      "output": {
        "type": 0,
        "typeArguments": null
      },
      "attributes": [
        {
          "name": "doc-comment",
          "arguments": [" this is a doc comment"]
        },
        {
          "name": "payable",
        },
        {
          "name": "storage",
          "arguments": ["read", "write"]
        }
      ]
    }
  ],
  "loggedTypes": []
}

An Example with Generic Types

Given the following ABI declaration:

#![allow(unused)]
fn main() {
enum MyEnum<T, U> {
    Foo: T,
    Bar: U,
}
struct MyStruct<W> {
    bam: MyEnum<W, W>,
}

abi MyContract {
    fn complex_function(
        arg1: MyStruct<b256>,
    );
}
}

its JSON representation would look like:

{
  "types": [
    {
      "typeId": 0,
      "type": "()",
      "components": [],
      "typeParameters": null
    },
    {
      "typeId": 1,
      "type": "b256",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 2,
      "type": "enum MyEnum",
      "components": [
        {
          "name": "Foo",
          "type": 3,
          "typeArguments": null
        },
        {
          "name": "Bar",
          "type": 4,
          "typeArguments": null
        }
      ],
      "typeParameters": [3, 4]
    },
    {
      "typeId": 3,
      "type": "generic T",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 4,
      "type": "generic U",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 5,
      "type": "generic W",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 6,
      "type": "struct MyStruct",
      "components": [
        {
          "name": "bam",
          "type": 2,
          "typeArguments": [
            {
              "type": 5,
              "typeArguments": null
            },
            {
              "type": 5,
              "typeArguments": null
            }
          ]
        }
      ],
      "typeParameters": [5]
    }
  ],
  "functions": [
    {
      "inputs": [
        {
          "name": "arg1",
          "type": 6,
          "typeArguments": [
            {
              "type": 1,
              "typeArguments": null
            }
          ]
        }
      ],
      "name": "complex_function",
      "output": {
        "type": 0,
        "typeArguments": null
      }
    }
  ],
  "loggedTypes": []
}

An Example with Logs

Given the following contract:

#![allow(unused)]
fn main() {
struct MyStruct<W> {
    x: W,
}

abi MyContract {
    fn logging();
}

...

fn logging() {
    log(MyStruct { x: 42 });
    log(MyStruct { x: true });
}
}

its JSON representation would look like:

{
  "types": [
    {
      "typeId": 0,
      "type": "()",
      "components": [],
      "typeParameters": null
    },
    {
      "typeId": 1,
      "type": "bool",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 2,
      "type": "generic W",
      "components": null,
      "typeParameters": null
    },
    {
      "typeId": 3,
      "type": "struct MyStruct",
      "components": [
        {
          "name": "x",
          "type": 2,
          "typeArguments": null
        }
      ],
      "typeParameters": [2]
    },
    {
      "typeId": 4,
      "type": "u64",
      "components": null,
      "typeParameters": null
    }
  ],
  "functions": [
    {
      "inputs": [],
      "name": "logging",
      "output": {
        "type": 0,
        "typeArguments": null
      }
    }
  ],
  "loggedTypes": [
    {
      "logId": 0,
      "loggedType": {
        "type": 3,
        "typeArguments": [
          {
            "type": 4,
            "typeArguments": null
          }
        ]
      }
    },
    {
      "logId": 1,
      "loggedType": {
        "type": 3,
        "typeArguments": [
          {
            "type": 1,
            "typeArguments": null
          }
        ]
      }
    }
  ]
}

Receipts

Upon execution of ABI calls, i.e scripts being executed, a JSON object representing a list of receipts will be returned to the caller. Below is the JSON specification of the possible receipt types. The root will be receipts_root which will include an array of receipts.

{
  "receipts_list":[
    {
      "type":"<receipt_type>",
      ...
    },
    ...
  ]
}

All receipts will have a type property:

Then, each receipt type will have its own properties. Some of these properties are related to the FuelVM's registers, as specified in more detail here.

Important note: For the JSON representation of receipts, we represent 64-bit unsigned integers as a JSON String due to limitations around the type Number in the JSON specification, which only supports numbers up to 2^{53-1}, while the FuelVM's registers hold values up to 2^64.

Panic Receipt

  • type: Panic.
  • id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • reason: Decimal string representation of an 8-bit unsigned integer; panic reason.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
  • contractId: Optional hexadecimal string representation of the 256-bit (32-byte) contract ID if applicable. null otherwise. Not included in canonical receipt form. Primary use is for access-list estimation by SDK.
{
  "type": "Panic",
  "id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "reason": "1",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe",
  "contractId": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

Return Receipt

  • type: Return.
  • id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context; null otherwise.
  • val: Decimal string representation of a 64-bit unsigned integer; value of register $rA.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Return",
  "id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "val": "18446744073709551613",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

Call Receipt

  • type: Call.
  • from: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context; null otherwise.
  • to: Hexadecimal representation of the 256-bit (32-byte) contract ID of the callee.
  • amount: Decimal string representation of a 64-bit unsigned integer; amount of coins to forward.
  • asset_id: Hexadecimal string representation of the 256-bit (32-byte) asset ID of coins to forward.
  • gas: Decimal string representation of a 64-bit unsigned integer; amount gas to forward; value in register $rD.
  • param1: Hexadecimal string representation of a 64-bit unsigned integer; first parameter, holds the function selector.
  • param2: Hexadecimal string representation of a 64-bit unsigned integer; second parameter, typically used for the user-specified input to the ABI function being selected.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Call",
  "from": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "to": "0x1c98ff5d121a6d5afc8135821acb3983e460ef0590919266d620bfc7b9b6f24d",
  "amount": "10000",
  "asset_id": "0xa5149ac6064222922eaa226526b0d853e7871e28c368f6afbcfd60a6ef8d6e61",
  "gas": "500",
  "param1": "0x28f5c28f5c28f5c",
  "param2": "0x68db8bac710cb",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

Log Receipt

  • type: Log.
  • id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • val0: Decimal string representation of a 64-bit unsigned integer; value of register $rA.
  • val1: Decimal string representation of a 64-bit unsigned integer; value of register $rB.
  • val2: Decimal string representation of a 64-bit unsigned integer; value of register $rC.
  • val3: Decimal string representation of a 64-bit unsigned integer; value of register $rD.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Log",
  "id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "val0": "1844674407370",
  "val1": "1844674407371",
  "val2": "1844674407372",
  "val3": "1844674407373",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

Mint Receipt

  • type: Mint.
  • sub_id: Hexadecimal string representation of the 256-bit (32-byte) asset sub identifier ID; derived from register $rB.
  • contract_id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context.
  • val: Decimal string representation of a 64-bit unsigned integer; value of register $rA.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Mint",
  "sub_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "contract_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "val": "18446744073709551613",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

Burn Receipt

  • type: Burn.
  • sub_id: Hexadecimal string representation of the 256-bit (32-byte) asset sub identifier ID; derived from register $rB.
  • contract_id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context.
  • val: Decimal string representation of a 64-bit unsigned integer; value of register $rA.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Burn",
  "sub_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "contract_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "val": "18446744073709551613",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

LogData Receipt

  • type: LogData.
  • id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • val0: Decimal string representation of a 64-bit unsigned integer; value of register $rA
  • val1: Decimal string representation of a 64-bit unsigned integer; value of register $rB
  • ptr: Hexadecimal string representation of a 64-bit unsigned integer; value of register $rC.
  • len: Decimal string representation of a 64-bit unsigned integer; value of register $rD.
  • digest: Hexadecimal string representation of the 256-bit (32-byte) hash of MEM[$rC, $rD].
  • data: Hexadecimal string representation of the value of the memory range MEM[$rC, $rD].
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "LogData",
  "id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "val0": "1844674407370",
  "val1": "1844674407371",
  "ptr": "0x1ad7f29abcc",
  "len": "66544",
  "digest": "0xd28b78894e493c98a196aa51b432b674e4813253257ed9331054ee8d6813b3aa",
  "pc": "0xffffffffffffffff",
  "data": "0xa7c5ac471b47",
  "is": "0xfffffffffffffffe"
}

ReturnData Receipt

  • type: ReturnData.
  • id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • ptr: Hexadecimal string representation of a 64-bit unsigned integer; value of register $rA.
  • len: Decimal string representation of a 64-bit unsigned integer; value of register $rB.
  • digest: Hexadecimal string representation of 256-bit (32-byte), hash of MEM[$rA, $rB].
  • data: Hexadecimal string representation of the value of the memory range MEM[$rA, $rB].
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "ReturnData",
  "id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "ptr": "0x1ad7f29abcc",
  "len": "1844",
  "digest": "0xd28b78894e493c98a196aa51b432b674e4813253257ed9331054ee8d6813b3aa",
  "pc": "0xffffffffffffffff",
  "data": "0xa7c5ac471b47",
  "is": "0xfffffffffffffffe"
}

Revert Receipt

  • type: Revert.
  • id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • val: Decimal string representation of a 64-bit unsigned integer; value of register $rA.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Revert",
  "id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "val": "1844674407372",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

Transfer Receipt

  • type: Transfer.
  • from: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • to: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the recipient contract.
  • amount: Decimal string representation of a 64-bit unsigned integer; amount of coins to forward.
  • asset_id: Hexadecimal string representation of the 256-bit (32-byte) asset ID of coins to forward.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "Transfer",
  "from": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "to": "0x1c98ff5d121a6d5afc8135821acb3983e460ef0590919266d620bfc7b9b6f24d",
  "amount": "10000",
  "asset_id": "0xa5149ac6064222922eaa226526b0d853e7871e28c368f6afbcfd60a6ef8d6e61",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

TransferOut Receipt

  • type: TransferOut.
  • from: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context. null otherwise.
  • to: Hexadecimal string representation of the 256-bit (32-byte) address to transfer coins to.
  • amount: Decimal string representation of a 64-bit unsigned integer; amount of coins to forward.
  • asset_id: Hexadecimal string representation of the 256-bit (32-byte) asset ID of coins to forward.
  • pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register $pc.
  • is: Hexadecimal string representation of a 64-bit unsigned integer; value of register $is.
{
  "type": "TransferOut",
  "from": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "to": "0x1c98ff5d121a6d5afc8135821acb3983e460ef0590919266d620bfc7b9b6f24d",
  "amount": "10000",
  "asset_id": "0xa5149ac6064222922eaa226526b0d853e7871e28c368f6afbcfd60a6ef8d6e61",
  "pc": "0xffffffffffffffff",
  "is": "0xfffffffffffffffe"
}

ScriptResult Receipt

  • type: ScriptResult.
  • result: Hexadecimal string representation of a 64-bit unsigned integer; 0 if script exited successfully, any otherwise.
  • gas_used: Decimal string representation of a 64-bit unsigned integer; amount of gas consumed by the script.
{
  "type": "ScriptResult",
  "result": "0x00",
  "gas_used": "400"
}

MessageOut Receipt

  • type: MessageOut.
  • sender: Hexadecimal string representation of the 256-bit (32-byte) address of the message sender: MEM[$fp, 32].
  • recipient: Hexadecimal string representation of the 256-bit (32-byte) address of the message recipient: MEM[$rA, 32].
  • amount: Hexadecimal string representation of a 64-bit unsigned integer; value of register $rD.
  • nonce: Hexadecimal string representation of the 256-bit (32-byte) message nonce as described here.
  • len: Decimal string representation of a 16-bit unsigned integer; value of register $rC.
  • digest: Hexadecimal string representation of 256-bit (32-byte), hash of MEM[$rB, $rC].
  • data: Hexadecimal string representation of the value of the memory range MEM[$rB, $rC].
{
  "type": "MessageOut",
  "sender": "0x38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff05139150017c9e",
  "recipient": "0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
  "amount": "0xe6d8fe4710162c2e",
  "nonce": "0x47101017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
  "len": "65535",
  "digest": "0xd28b78894e493c98a196aa51b432b674e4813253257ed9331054ee8d6813b3aa",
  "data": "0xa7c5ac471b47"
}

Function Selector Encoding

To select which function you want to call, first, this function must be in an ABI struct of a Sway program. For instance:

#![allow(unused)]
fn main() {
abi MyContract {
    fn foo(a: u64);
    fn bar(a: InputStruct );
} {
    fn baz(a: ()) { }
}
}

The function selector is the first 4 bytes of the SHA-256 hash function of the signature of the Sway function being called. Then, these 4 bytes are right-aligned to 8 bytes, left-padded with zeroes.

Note: The word size for the FuelVM is 8 bytes.

Function Signature

The signature is composed of the function name with the parenthesized list of comma-separated parameter types without spaces. All strings encoded with UTF-8. For custom types such as enum and struct, there is a prefix added to the parenthesized list (see below). Generic struct and enum types also accept a list of comma-separated type arguments in between angle brackets right after the prefix.

For instance, to compute the selector for the following function:

#![allow(unused)]
fn main() {
    fn entry_one(arg: u64);
}

we should pass "entry_one(u64)" to the sha256() hashing algorithm. The full digest would be:

0x0c36cb9cb766ff60422db243c4fff06d342949da3c64a3c6ac564941f84b6f06

Then we would get only the first 4 bytes of this digest and left-pad it to 8 bytes:

0x000000000c36cb9c

The table below summarizes how each function argument type is encoded

TypeEncoding
boolbool
u8u8
u16u16
u32u32
u64u64
b256b256
structs<<arg1>,<arg2>,...>(<ty1>,<ty2>,...) where <ty1>, <ty2>, ... are the encoded types of the struct fields and <arg1>, <arg2>, ... are the encoded type arguments
enume<<arg1>>,<arg_2>,...>(<ty1>,<ty2>,...) where <ty1>, <ty2>, ... are the encoded types of the enum variants and <arg1>, <arg2>, ... are the encoded type arguments
str[<n>]str[<n>]
arraya[<ty>;<n>] where <ty> is the encoded element type of the array and <n> is its length
tuple(<ty1>,<ty2>,...) where <ty1>, <ty2>, ... are the encoded types of the tuple fields

Note: Non-generic structs and enums do not require angle brackets.

A Complex Example

#![allow(unused)]
fn main() {
enum MyEnum<V> {
    Foo: u64,
    Bar: bool,
}
struct MyStruct<T, U> {
    bim: T,
    bam: MyEnum<u64>,
}

struct MyOtherStruct {
    bom: u64,
}

fn complex_function(
    arg1: MyStruct<[b256; 3], u8>,
    arg2: [MyStruct<u64, bool>; 4],
    arg3: (str[5], bool),
    arg4: MyOtherStruct,
);
}

is encoded as:

abi MyContract {
    complex_function(s<a[b256;3],u8>(a[b256;3],e<u64>(u64,bool)),a[s<u64,bool>(u64,e<u64>(u64,bool));4],(str[5],bool),s(u64))
}

which is then hashed into:

51fdfdadc37ff569e281a622281af7ec055f8098c40bc566118cbb48ca5fd28b

and then the encoded function selector is:

0x0000000051fdfdad

Argument Encoding

Version 0

:warning: This version is being deprecated for Version 1 (see below).

When crafting transaction script data, you must encode the arguments you wish to pass to the script.

Types

These are the available types that can be encoded in the ABI:

  • Unsigned integers:
    • u8, 8 bits.
    • u16, 16 bits.
    • u32, 32 bits.
    • u64, 64 bits.
    • u128, 128 bits.
    • u256, 256 bits.
  • Boolean: bool, either 0 or 1 encoded identically to u8.
  • B256: b256, arbitrary 256-bits value.
  • Address : address, a 256-bit (32-byte) address.
  • Fixed size string
  • Array
  • Enums (sum types)
  • Structs
  • Vectors
  • Tuples

These types are encoded in-place. Here's how to encode them. We define enc(X) the encoding of the type X.

Unsigned Integers

u<M> where M is either 8, 16, 32, 64, 128 or 256 bits.

enc(X) is the big-endian (i.e. right-aligned) representation of X left-padded with zero-bytes.

  • In the case of M being 8, 16, 32 or 64, total length must be 8 bytes.
  • If M is 128, total length must be 16 bytes.
  • If M is 256, total length must be 32 bytes.

Note: since all integer values are unsigned, there is no need to preserve the sign when extending/padding; padding with only zeroes is sufficient._

Example:

Encoding 42 yields: 0x000000000000002a, which is the hexadecimal representation of the decimal number 42, right-aligned to 8 bytes. Encoding u128::MAX - 1 yields: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, it's right-aligned to 16 bytes

Boolean

enc(X) is 0 if X is false or 1 if X is true, left-padded with zero-bytes. Total length must be 8 bytes. Similar to the u8 encoding.

Example:

Encoding true yields:

0x0000000000000001

B256

b256 is a fixed size bit array of length 256. Used for 256-bit hash digests and other 256-bit types. It is encoded as-is.

Example:

Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745 yields:

0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745

Address

A 256-bit (32-byte) address, encoded in the same way as a b256 argument: encoded as-is.

Example:

Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745 yields:

0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745

Array

An array of a certain type T, [T; n], where n is the length of the array.

Arrays in Sway have a fixed-length which is known at compile time. This means the ABI encoding for arrays also happens in-place, with no need to account for dynamic sizing.

The encoding for the array contains, in order, the encoding of each element in [T; n], recursively following the encoding for the type T.

For instance, consider the function signature my_func(bool, [u64; 2]) with the values (true, [1, 2]).

The encoding will be:

  1. 0x0000000000000001, the true bool encoded in-place.
  2. 0x0000000000000001, First element of the parameter [u64; 2], 1, encoded as a u64.
  3. 0x0000000000000002, Second element of the parameter [u64; 2], 2, encoded as a u64.

The resulting encoded ABI will be:

0x000000000000000100000000000000010000000000000002

Fixed-length Strings

Strings have fixed length and are encoded in-place. str[n], where n is the fixed-size of the string. Rather than padding the string, the encoding of the elements of the string is tightly packed. And unlike the other type encodings, the string encoding is left-aligned.

Note that all strings are encoded in UTF-8.

Example:

Encoding "Hello, World" as a str[12] yields:

0x48656c6c6f2c20576f726c6400000000

Note that we're padding with zeroes in order to keep it right-aligned to 8 bytes (FuelVM's word size).

Structs

Encoding ABIs that contain custom types, such as structs, is similar to encoding a set of primitive types. The encoding will proceed in the order that the inner types of a custom type are declared and recursively just like encoding any other type. This way you can encode structs with primitive types or structs with more complex types in them such as other structs, arrays, strings, and enums.

Here's an example:

#![allow(unused)]
fn main() {
struct InputStruct {
    field_1: bool,
    field_2: u8,
}


abi MyContract {
    fn foo(a: u64);
    fn bar(a: InputStruct);
} {
    fn baz(a: ()) { }
}
}

Calling bar with InputStruct { field_1: true, field_2: 5 } yields:

0x
0000000000000001 // `true` encoded as a bool
0000000000000005 // `5` encoded as u8

A more complex scenario:

#![allow(unused)]
fn main() {
struct InputStruct {
    field_1: bool,
    field_2: [u8; 2], // An array of u8, with length 2.
}


abi MyContract {
    fn foo(a: u64);
    fn bar(a: InputStruct);
} {
    fn baz(a: ()) { }
}
}

Calling bar with InputStruct { field_1: true, field_2: [1, 2] } yields:

0x
0000000000000001 // `true` encoded as a bool
0000000000000001 // `1` encoded as u8
0000000000000002 // `2` encoded as u8

Enums (sum types)

ABI calls containing enums (sum types) are encoded similarly to structs: encode the discriminant first, then recursively encode the type of the enum variant being passed to the function being called.

#![allow(unused)]
fn main() {
enum MySumType
{
    X: u32,
    Y: bool,
}

abi MyContract {
    fn foo(a: u64);
    fn bar(a: MySumType);
} {
    fn baz(a: ()) { }
}
}

Calling bar with MySumType::X(42) yields:

0x
0000000000000000 // The discriminant of the chosen enum, in this case `0`.
000000000000002a // `42` encoded as u64

If the sum type has variants of different sizes, then left padding is required.

#![allow(unused)]
fn main() {
enum MySumType
{
    X: b256,
    Y: u32,
}

abi MyContract {
    fn foo(a: u64);
    fn bar(a: MySumType);
} {
    fn baz(a: ()) { }
}
}

Calling bar with MySumType::Y(42) yields:

0x
0000000000000001 // The discriminant of the chosen enum, in this case `1`.
0000000000000000 // Left padding
0000000000000000 // Left padding
0000000000000000 // Left padding
000000000000002a // `42` encoded as u64

Note that three words of padding are required because the largest variant of MySumType is 4 words wide.

If all the variants of a sum type are of type (), or unit, then only the discriminant needs to be encoded.

#![allow(unused)]
fn main() {
enum MySumType
{
    X: (),
    Y: (),
    Z: (),
}

abi MyContract {
    fn foo(a: u64);
    fn bar(a: MySumType);
} {
    fn baz(a: ()) { }
}
}

Calling bar with MySumType::Z yields:

0x
0000000000000002 // The discriminant of the chosen enum, in this case `2`.

Vectors

ABI calls containing vectors are encoded in the following way:

  • First, figure out the the length l of the vector. Its length will also be its capacity.
  • Encode the content of the vector according to the spec of its type, e.g. for a Vec<bool>, encode each bool element according to the bool specs. This gives out data that is stored on the heap, and we encode only the pointer to this data
#![allow(unused)]
fn main() {
abi MyContract {
  fn foo(a: Vec<u32>);
} {
  fn foo(a: Vec<u32>) {};
}
}

Calling foo with vec![1u32, 2u32, 3u32, 4u32]:

  • length is 4, capacity is 4
  • data: [0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x0000000000000004], stored at pointer address 0x000000000000beef

Note: to understand how the u32 are encoded, see the section about encoding integers.

0x
000000000000beef // pointer address
0000000000000004 // length = 4
0000000000000004 // capacity = 4

At the pointer address, then the vector's content are encoded as such:

0x
0000000000000001 // 1u32
0000000000000002 // 2u32
0000000000000003 // 3u32
0000000000000004 // 4u32

Tuples

ABI calls containing tuples are encoded as such: If X is a tuple with the type signature (T_1, T_2, ..., T_n), with T_1,...,T_n being any type except for vector, then enc(X) is encoded as the concatenation of enc(T_1), enc(T_2),enc (T_3), ..., enc(T_n).

#![allow(unused)]
fn main() {
abi MyContract {
  fn foo(a: (u64, str[3], bool));
} {
  fn foo(a: (u64, str[4], bool)) {};
}
}

Calling foo with (1u64, "fuel", true) :

0x
0000000000000001 // 1u64
6675656c00000000 // "fuel" encoded as per the specs
0000000000000001 // true

Version 1

This version was created to replace the older version 0 described above, and follows three philosophical tenets:

  • being self-sufficient: it must be possible to completely decode the original data only using the encoded bytes and the original type (there are no references to data outside the encoded bytes);
  • no overhead: only the bare minimum bytes are necessary to do the encoding. No metadata, headers, etc...;
  • no relation with runtime memory layout: no padding, no alignment, etc...

Primitive Types

Primitive types will be encoded using the exact number of bits they need:

  • u8: 1 byte;
  • u16: 2 bytes;
  • u32: 4 bytes;
  • u64: 8 bytes;
  • u256: 32 bytes;
  • b256: 32 bytes;

Arrays

Arrays are encoded without any padding or alignment, with one item after the other.

  • [T; 1] is encoded [encode(T)];
  • [T; 2] is encoded [encode(T), encode(T)]

Strings

String arrays are encoded just like arrays, without any overhead.

  • str[1] = 1 byte
  • str[2] = 2 bytes
  • str[n] = n bytes

String slices do contain their length as u64, and the string itself is encoded packed without alignment or padding.

  • "abc" = [0, 0, 0, 0, 0, 0, 0, 3, "a", "b", "c"]

Slices

raw_slice, also being dynamic, contains their length as u64 and is treated as a "slice of bytes". Each byte is encoded as u8 (1 byte) and is packed without alignment and padding.

For example, a slice of three bytes like [0u8, 1u8, 2u8] will be encoded as bytes [0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 2]

Tuple

Tuples are encoded just like arrays, without any overhead like padding and alignment:

  • (A, B, C) = [encode(A), encode(B), encode(C)]

Structs (v1)

Structs can be encoded in two ways:

  • first, with the automatic implementation;
  • second, with the custom implementation.

Auto implementation follows the same rules as tuples. So we can imagine that

struct S {
    a: A,
    b: B,
    c: C
}

is encoded the same way as the tuple (A, B, C).

Custom implementation allows the developer to choose how a struct is encoded.

A struct has auto-implemented encoding if no custom was found.

Enums

Enums can also be encoded with the automatic or the custom implementation.

The auto implementation first encoded the variant with a u64 number starting from zero as the first variant and increments this value for each variant, following declaration order.

enum E {
    VARIANT_A: A, // <- variant 0
    VARIANT_B: B, // <- variant 1
    VARIANT_C: C  // <- variant 2 
}

will be encoded as [encode(variant), encode(value)].

The variant data will be encoded right after the variant tag, without any alignment or padding.

An enum has auto-implemented encoding if no custom was found.

Data Structures

Some common data structures also have well-defined encoding:

  • Vec will be encoded as [encode(length), <encode each item>]
  • Bytes will be encoded as [encode(length), <bytes>]
  • String will be encoded as [encode (length), <data>]

All of them first contain the length and then their data right after, without any padding or alignment.

Fuel VM Specification

Introduction

This document provides the specification for the Fuel Virtual Machine (FuelVM). The specification covers the types, instruction set, and execution semantics.

Parameters

nametypevaluenote
CONTRACT_MAX_SIZEuint64Maximum contract size, in bytes.
MEM_MAX_ACCESS_SIZEuint64Maximum memory access size, in bytes.
VM_MAX_RAMuint642**2664 MiB.
MESSAGE_MAX_DATA_SIZEuint64Maximum size of message data, in bytes.

Semantics

FuelVM instructions are exactly 32 bits (4 bytes) wide and comprise of a combination of:

  • Opcode: 8 bits
  • Register/special register (see below) identifier: 6 bits
  • Immediate value: 12, 18, or 24 bits, depending on operation

Of the 64 registers (6-bit register address space), the first 16 are reserved:

valueregisternamedescription
0x00$zerozeroContains zero (0), for convenience.
0x01$oneoneContains one (1), for convenience.
0x02$ofoverflowContains overflow/underflow of addition, subtraction, and multiplication.
0x03$pcprogram counterThe program counter. Memory address of the current instruction.
0x04$sspstack start pointerMemory address of bottom of current writable stack area.
0x05$spstack pointerMemory address on top of current writable stack area (points to free memory).
0x06$fpframe pointerMemory address of beginning of current call frame.
0x07$hpheap pointerMemory address below the current bottom of the heap (points to used/OOB memory).
0x08$errerrorError codes for particular operations.
0x09$ggasglobal gasRemaining gas globally.
0x0A$cgascontext gasRemaining gas in the context.
0x0B$balbalanceReceived balance for this context.
0x0C$isinstructions startPointer to the start of the currently-executing code.
0x0D$retreturn valueReturn value or pointer.
0x0E$retlreturn lengthReturn value length in bytes.
0x0F$flagflagsFlags register.

Integers are represented in big-endian format, and all operations are unsigned. Boolean false is 0 and Boolean true is 1.

Registers are 64 bits (8 bytes) wide. Words are the same width as registers.

Persistent state (i.e. storage) is a key-value store with 32-byte keys and 32-byte values. Each contract has its own persistent state that is independent of other contracts. This is committed to in a Sparse Binary Merkle Tree.

Flags

valuenamedescription
0x01F_UNSAFEMATHIf set, undefined arithmetic zeroes target and sets $err without panic.
0x02F_WRAPPINGIf set, overflowing arithmetic wraps around and sets $of without panic.

All other flags are reserved, any must be set to zero.

Instruction Set

A complete instruction set of the Fuel VM is documented in the following page.

VM Initialization

Every time the VM runs, a single monolithic memory of size VM_MAX_RAM bytes is allocated, indexed by individual byte. A stack and heap memory model is used, allowing for dynamic memory allocation in higher-level languages. The stack begins at 0 and grows upward. The heap begins at VM_MAX_RAM and grows downward.

To initialize the VM, the following is pushed on the stack sequentially:

  1. Transaction hash (byte[32], word-aligned), computed as defined here.
  2. Base asset ID (byte[32], word-aligned)
  3. MAX_INPUTS pairs of (asset_id: byte[32], balance: uint64), of:
    1. For predicate estimation and predicate verification, zeroes.
    2. For script execution, the free balance for each asset ID seen in the transaction's inputs, ordered in ascending order. If there are fewer than MAX_INPUTS asset IDs, the pair has a value of zero.
  4. Transaction length, in bytes (uint64, word-aligned).
  5. The transaction, serialized.

Then the following registers are initialized (without explicit initialization, all registers are initialized to zero):

  1. $ssp = 32 + 32 + MAX_INPUTS*(32+8) + size(tx)): the writable stack area starts immediately after the serialized transaction in memory (see above).
  2. $sp = $ssp: writable stack area is empty to start.
  3. $hp = VM_MAX_RAM: the heap area begins at the top and is empty to start.

Contexts

There are 4 contexts in the FuelVM: predicate estimation, predicate verification, scripts, and calls. A context is an isolated execution environment with defined memory ownership and can be external or internal:

  • External: predicate and script. $fp will be zero.
  • Internal: call. $fp will be non-zero.

Returning from a context behaves differently depending on whether the context is external or internal.

Predicate Estimation

For any input of type InputType.Coin or InputType.Message, a non-zero predicateLength field means the UTXO being spent is a P2SH rather than a P2PKH output.

For each such input in the transaction, the VM is initialized, then:

  1. $pc and $is are set to the start of the input's predicate field.
  2. $ggas and $cgas are set to MAX_GAS_PER_PREDICATE.

Predicate estimation will fail if gas is exhausted during execution.

During predicate mode, hitting any of the following instructions causes predicate estimation to halt, returning Boolean false:

  1. Any contract instruction.

In addition, during predicate mode if $pc is set to a value greater than the end of predicate bytecode (this would allow bytecode outside the actual predicate), predicate estimation halts returning Boolean false.

A predicate that halts without returning Boolean true would not pass verification, making the entire transaction invalid. Note that predicate validity is monotonic with respect to time (i.e. if a predicate evaluates to true then it will always evaluate to true in the future).

After successful execution, predicateGasUsed is set to MAX_GAS_PER_PREDICATE - $ggas.

Predicate Verification

For any input of type InputType.Coin or InputType.Message, a non-zero predicateLength field means the UTXO being spent is a P2SH rather than a P2PKH output.

For each such input in the transaction, the VM is initialized, then:

  1. $pc and $is are set to the start of the input's predicate field.
  2. $ggas and $cgas are set to predicateGasUsed.

Predicate verification will fail if gas is exhausted during execution.

During predicate mode, hitting any contract instruction causes predicate verification to halt, returning Boolean false.

In addition, during predicate mode if $pc is set to a value greater than the end of predicate bytecode (this would allow bytecode outside the actual predicate), predicate verification halts returning Boolean false.

A predicate that halts without returning Boolean true does not pass verification, making the entire transaction invalid. Note that predicate validity is monotonic with respect to time (i.e. if a predicate evaluates to true then it will always evaluate to true in the future).

After execution, if $ggas is non-zero, predicate verification fails.

Script Execution

If script bytecode is present, transaction validation requires execution.

The VM is initialized, then:

  1. $pc and $is are set to the start of the transaction's script bytecode.
  2. $ggas and $cgas are set to tx.scriptGasLimit.

Following initialization, execution begins.

For each instruction, its gas cost gc is first computed. If gc > $cgas, deduct $cgas from $ggas and $cgas (i.e. spend all of $cgas and no more), then revert immediately without actually executing the instruction. Otherwise, deduct gc from $ggas and $cgas.

After the script has been executed, tx.receiptsRoot is updated to contain the Merkle root of the receipts, as described in the TransactionScript spec.

Call Frames

Cross-contract calls push a call frame onto the stack, similar to a stack frame used in regular languages for function calls (which may be used by a high-level language that targets the FuelVM). The distinction is as follows:

  1. Stack frames: store metadata across trusted internal (i.e. intra-contract) function calls. Not supported natively by the FuelVM, but may be used as an abstraction at a higher layer.
  2. Call frames: store metadata across untrusted external (i.e. inter-contract) calls. Supported natively by the FuelVM.

Call frames are needed to ensure that the called contract cannot mutate the running state of the current executing contract. They segment access rights for memory: the currently-executing contracts may only write to their own call frame and their own heap.

A call frame consists of the following, word-aligned:

bytestypevaluedescription
Unwritable area begins.
32byte[32]toContract ID for this call.
32byte[32]asset_idasset ID of forwarded coins.
8*64byte[8][64]regsSaved registers from previous context.
8uint64codesizeCode size in bytes.
8byte[8]param1First parameter.
8byte[8]param2Second parameter.
1*byte[]codeZero-padded to 8-byte alignment, but individual instructions are not aligned.
Unwritable area ends.
*Call frame's stack.

Access rights

Only memory that has been allocated is accessible. Attempting to read or write memory that has not been allocated will result in VM panic. Similarly reads or writes that cross from the stack to the heap will panic. Note stack remains readable even after stack frame has been shrunk. However, if heap is alter expanded to cover that area, the crossing read prohibition still remains. In other word, memory between highest-ever $sp value and current $hp is inaccessible.

Ownership

Whenever memory is written to (i.e. with SB or SW), or write access is granted (i.e. with CALL), ownership must be checked.

If the context is external, the owned memory range is:

  1. [$ssp, $sp): the writable stack area.
  2. [$hp, VM_MAX_RAM): the heap area allocated by this script or predicate.

If the context is internal, the owned memory range for a call frame is:

  1. [$ssp, $sp): the writable stack area of the call frame.
  2. [$hp, $fp->$hp): the heap area allocated by this call frame.

Executablity

Memory is only executable in range [$is, $ssp). Attempting to execute instructions outside these boundaries will panic. This area never overlaps with writable registers, essentially providing W^X protection.

FuelVM Instruction Set

Reading Guide

This page provides a description of all instructions for the FuelVM. Encoding is read as a sequence of one 8-bit value (the opcode identifier) followed by four 6-bit values (the register identifiers or immediate value). A single i indicates a 6-bit immediate value, i i indicates a 12-bit immediate value, i i i indicates an 18-bit immediate value, and i i i i indicates a 24-bit immediate value. All immediate values are interpreted as big-endian unsigned integers.

  • The syntax MEM[x, y] used in this page means the memory range starting at byte x, of length y bytes.
  • The syntax STATE[x, y] used in this page means the sequence of storage slots starting at key x and spanning y bytes.

Panics

Some instructions may panic, i.e. enter an unrecoverable state. Additionally, attempting to execute an instruction not in this list causes a panic and consumes no gas. How a panic is handled depends on context:

  • In a predicate context, cease VM execution and return false.
  • In other contexts, revert (described below).

On a non-predicate panic, append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Panic
idbyte[32]Contract ID of current context if in an internal context, zero otherwise.
pcuint64Value of register $pc.
isuint64Value of register $is.

then append an additional receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.ScriptResult
resultuint641
gas_useduint64Gas consumed by the script.

Receipts

The number of receipts is limited to 216, with the last two reserved to panic and script result receipts. Trying to add any other receipts after 216-2 will panic.

Effects

A few instructions are annotated with the effects they produce, the table below explains each effect:

effect namedescription
Storage readInstruction reads from storage slots
Storage writeInstruction writes to storage slots
External callExternal contract call instruction
Balance tree readInstruction reads from the balance tree
Balance tree writeInstruction writes to the balance tree
Output messageInstruction sends a message to a recipient address

If an instruction is not annotated with an effect, it means it does not produce any of the aforementioned affects.

Arithmetic/Logic (ALU) Instructions

All these instructions advance the program counter $pc by 4 after performing their operation.

Normally, if the result of an ALU operation is mathematically undefined (e.g. dividing by zero), the VM panics. However, if the F_UNSAFEMATH flag is set, $err is set to true and execution continues.

If an operation would overflow, so that the result doesn't fit into the target field, the VM will panic. Results below zero are also considered overflows. If the F_WRAPPING flag is set, instead $of is set to true or the overflowing part of the result, depending on the operation.

ADD: Add

DescriptionAdds two registers.
Operation$rA = $rB + $rC;
Syntaxadd $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of is assigned the overflow of the operation.

$err is cleared.

ADDI: Add immediate

DescriptionAdds a register and an immediate value.
Operation$rA = $rB + imm;
Syntaxaddi $rA, $rB, immediate
Encoding0x00 rA rB i i
Notes

Panic if:

$of is assigned the overflow of the operation.

$err is cleared.

AND: AND

DescriptionBitwise ANDs two registers.
Operation$rA = $rB & $rC;
Syntaxand $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of and $err are cleared.

ANDI: AND immediate

DescriptionBitwise ANDs a register and an immediate value.
Operation$rA = $rB & imm;
Syntaxandi $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

imm is extended to 64 bits, with the high 52 bits set to 0.

$of and $err are cleared.

DIV: Divide

DescriptionDivides two registers.
Operation$rA = $rB // $rC;
Syntaxdiv $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

If $rC == 0, $rA is cleared and $err is set to true.

Otherwise, $err is cleared.

$of is cleared.

DIVI: Divide immediate

DescriptionDivides a register and an immediate value.
Operation$rA = $rB // imm;
Syntaxdivi $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

If imm == 0, $rA is cleared and $err is set to true.

Otherwise, $err is cleared.

$of is cleared.

EQ: Equals

DescriptionCompares two registers for equality.
Operation$rA = $rB == $rC;
Syntaxeq $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of and $err are cleared.

EXP: Exponentiate

DescriptionRaises one register to the power of another.
Operation$rA = $rB ** $rC;
Syntaxexp $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

If the result cannot fit in 8 bytes, $of is set to 1 and $rA is instead set to 0, otherwise $of is cleared.

$err is cleared.

EXPI: Exponentiate immediate

DescriptionRaises one register to the power of an immediate value.
Operation$rA = $rB ** imm;
Syntaxexpi $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

If the result cannot fit in 8 bytes, $of is set to 1 and $rA is instead set to 0, otherwise $of is cleared.

$err is cleared.

GT: Greater than

DescriptionCompares two registers for greater-than.
Operation$rA = $rB > $rC;
Syntaxgt $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of and $err are cleared.

LT: Less than

DescriptionCompares two registers for less-than.
Operation$rA = $rB < $rC;
Syntaxlt $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of and $err are cleared.

MLOG: Math logarithm

DescriptionThe (integer) logarithm base $rC of $rB.
Operation$rA = math.floor(math.log($rB, $rC));
Syntaxmlog $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

If $rB == 0, both $rA and $of are cleared and $err is set to true.

If $rC <= 1, both $rA and $of are cleared and $err is set to true.

Otherwise, $of and $err are cleared.

MOD: Modulus

DescriptionModulo remainder of two registers.
Operation$rA = $rB % $rC;
Syntaxmod $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

If $rC == 0, both $rA and $of are cleared and $err is set to true.

Otherwise, $of and $err are cleared.

MODI: Modulus immediate

DescriptionModulo remainder of a register and an immediate value.
Operation$rA = $rB % imm;
Syntaxmodi $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

If imm == 0, both $rA and $of are cleared and $err is set to true.

Otherwise, $of and $err are cleared.

MOVE: Move

DescriptionCopy from one register to another.
Operation$rA = $rB;
Syntaxmove $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

$of and $err are cleared.

MOVI: Move immediate

DescriptionCopy an immediate value into a register.
Operation$rA = imm;
Syntaxmovi $rA, imm
Encoding0x00 rA i i i
Notes

Panic if:

$of and $err are cleared.

MROO: Math root

DescriptionThe (integer) $rCth root of $rB.
Operation$rA = math.floor(math.root($rB, $rC));
Syntaxmroo $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

If $rC == 0, both $rA and $of are cleared and $err is set to true.

Otherwise, $of and $err are cleared.

MUL: Multiply

DescriptionMultiplies two registers.
Operation$rA = $rB * $rC;
Syntaxmul $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of is assigned the overflow of the operation.

$err is cleared.

MULI: Multiply immediate

DescriptionMultiplies a register and an immediate value.
Operation$rA = $rB * imm;
Syntaxmul $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

$of is assigned the overflow of the operation.

$err is cleared.

MLDV: Fused multiply-divide

DescriptionMultiplies two registers with arbitrary precision, then divides by a third register.
Operationa = (b * c) / d;
Syntaxmldv $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
NotesDivision by zero is treated as division by 1 << 64 instead.

If the divisor ($rD) is zero, then instead the value is divided by 1 << 64. This returns the higher half of the 128-bit multiplication result. This operation never overflows.

If the result of after the division doesn't fit into a register, $of is assigned the overflow of the operation. Otherwise, $of is cleared.

$err is cleared.

NOOP: No operation

DescriptionPerforms no operation.
Operation
Syntaxnoop
Encoding0x00 - - - -
Notes

$of and $err are cleared.

NOT: Invert

DescriptionBitwise NOT a register.
Operation$rA = ~$rB;
Syntaxnot $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

$of and $err are cleared.

OR: OR

DescriptionBitwise ORs two registers.
Operation$rA = $rB \| $rC;
Syntaxor $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of and $err are cleared.

ORI: OR immediate

DescriptionBitwise ORs a register and an immediate value.
Operation$rA = $rB \| imm;
Syntaxori $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

imm is extended to 64 bits, with the high 52 bits set to 0.

$of and $err are cleared.

SLL: Shift left logical

DescriptionLeft shifts a register by a register.
Operation$rA = $rB << $rC;
Syntaxsll $rA, $rB, $rC
Encoding0x00 rA rB rC -
NotesZeroes are shifted in.

Panic if:

$of and $err are cleared.

SLLI: Shift left logical immediate

DescriptionLeft shifts a register by an immediate value.
Operation$rA = $rB << imm;
Syntaxslli $rA, $rB, imm
Encoding0x00 rA rB i i
NotesZeroes are shifted in.

Panic if:

$of and $err are cleared.

SRL: Shift right logical

DescriptionRight shifts a register by a register.
Operation$rA = $rB >> $rC;
Syntaxsrl $rA, $rB, $rC
Encoding0x00 rA rB rC -
NotesZeroes are shifted in.

Panic if:

$of and $err are cleared.

SRLI: Shift right logical immediate

DescriptionRight shifts a register by an immediate value.
Operation$rA = $rB >> imm;
Syntaxsrli $rA, $rB, imm
Encoding0x00 rA rB i i
NotesZeroes are shifted in.

Panic if:

$of and $err are cleared.

SUB: Subtract

DescriptionSubtracts two registers.
Operation$rA = $rB - $rC;
Syntaxsub $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes$of is assigned the overflow of the operation.

Panic if:

$of is assigned the underflow of the operation, as though $of is the high byte of a 128-bit register.

$err is cleared.

SUBI: Subtract immediate

DescriptionSubtracts a register and an immediate value.
Operation$rA = $rB - imm;
Syntaxsubi $rA, $rB, imm
Encoding0x00 rA rB i i
Notes$of is assigned the overflow of the operation.

Panic if:

$of is assigned the underflow of the operation, as though $of is the high byte of a 128-bit register.

$err is cleared.

WDCM: 128-bit integer comparison

DescriptionCompare or examine two 128-bit integers using selected mode
Operationb = mem[$rB,16];
c = indirect?mem[$rC,16]:$rC;
$rA = cmp_op(b,c);
Syntaxwdcm $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The six-bit immediate value is used to select operating mode, as follows:

BitsShort nameDescription
...XXXmodeCompare mode selection
.XX...reservedReserved and must be zero
X.....indirectIs rhs operand ($rC) indirect or not

Then the actual operation that's performed:

modeNameDescription
0eqEquality (==)
1neInequality (!=)
2ltLess than (<)
3gtGreater than (>)
4lteLess than or equals (<=)
5gteGreater than or equals (>=)
6lzcLeading zero count the lhs argument (lzcnt). Discards rhs.
7-Reserved and must not be used

The leading zero count can be used to compute rounded-down log2 of a number using the following formula TOTAL_BITS - 1 - lzc(n). Note that log2(0) is undefined, and will lead to integer overflow with this method.

Clears $of and $err.

Panic if:

  • A reserved compare mode is given
  • $rA is a reserved register
  • $rB + 16 overflows or > VM_MAX_RAM
  • indirect == 1 and $rC + 16 overflows or > VM_MAX_RAM

WQCM: 256-bit integer comparison

DescriptionCompare or examine two 256-bit integers using selected mode
Operationb = mem[$rB,32];
c = indirect?mem[$rC,32]:$rC;
$rA = cmp_op(b,c);
Syntaxwqcm $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The immediate value is interpreted identically to WDCM.

Clears $of and $err.

Panic if:

  • A reserved compare mode is given
  • $rA is a reserved register
  • $rB + 32 overflows or > VM_MAX_RAM
  • indirect == 1 and $rC + 32 overflows or > VM_MAX_RAM

WDOP: Misc 128-bit integer operations

DescriptionPerform an ALU operation on two 128-bit integers
Operationb = mem[$rB,16];
c = indirect?mem[$rC,16]:$rC;
mem[$rA,16] = op(b,c);
Syntaxwdop $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The six-bit immediate value is used to select operating mode, as follows:

BitsShort nameDescription
...XXXopOperation selection, see below
.XX...reservedReserved and must be zero
X.....indirectIs rhs operand ($rC) indirect or not

Then the actual operation that's performed:

opNameDescription
0addAdd
1subSubtract
2notInvert bits (discards rhs)
3orBitwise or
4xorBitwise exclusive or
5andBitwise and
6shlShift left (logical)
7shrShift right (logical)

Operations behave $of and $err similarly to their 64-bit counterparts, except that $of is set to 1 instead of the overflowing part.

Panic if:

  • Reserved bits of the immediate are set
  • The memory range MEM[$rA, 16] does not pass ownership check
  • $rB + 16 overflows or > VM_MAX_RAM
  • indirect == 1 and $rC + 16 overflows or > VM_MAX_RAM

WQOP: Misc 256-bit integer operations

DescriptionPerform an ALU operation on two 256-bit integers
Operationb = mem[$rB,32];
c = indirect?mem[$rC,32]:$rC;
mem[$rA,32] = op(b,c);
Syntaxwqop $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The immediate value is interpreted identically to WDOP.

Operations behave $of and $err similarly to their 64-bit counterparts.

Panic if:

  • Reserved bits of the immediate are set
  • The memory range MEM[$rA, 32] does not pass ownership check
  • $rB + 32 overflows or > VM_MAX_RAM
  • indirect == 1 and $rC + 32 overflows or > VM_MAX_RAM

WDML: Multiply 128-bit integers

DescriptionPerform integer multiplication operation on two 128-bit integers.
Operationb=indirect0?mem[$rB,16]:$rB;
c=indirect1?mem[$rC,16]:$rC;
mem[$rA,16]=b*c;
Syntaxwdml $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The six-bit immediate value is used to select operating mode, as follows:

BitsShort nameDescription
..XXXXreservedReserved and must be zero
.X....indirect0Is lhs operand ($rB) indirect or not
X.....indirect1Is rhs operand ($rC) indirect or not

$of is set to 1 in case of overflow, and cleared otherwise.

$err is cleared.

Panic if:

  • Reserved bits of the immediate are set
  • The memory range MEM[$rA, 16] does not pass ownership check
  • indirect0 == 1 and $rB + 16 overflows or > VM_MAX_RAM
  • indirect1 == 1 and $rC + 16 overflows or > VM_MAX_RAM

WQML: Multiply 256-bit integers

DescriptionPerform integer multiplication operation on two 256-bit integers.
Operationb=indirect0?mem[$rB,32]:$rB;
c=indirect1?mem[$rC,32]:$rC;
mem[$rA,32]=b*c;
Syntaxwqml $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The immediate value is interpreted identically to WDML.

$of is set to 1 in case of overflow, and cleared otherwise.

$err is cleared.

Panic if:

  • Reserved bits of the immediate are set
  • The memory range MEM[$rA, 32] does not pass ownership check
  • indirect0 == 1 and $rB + 32 overflows or > VM_MAX_RAM
  • indirect1 == 1 and $rC + 32 overflows or > VM_MAX_RAM

WDDV: 128-bit integer division

DescriptionDivide a 128-bit integer by another.
Operationb = mem[$rB,16];
c = indirect?mem[$rC,16]:$rC;
mem[$rA,16] = b / c;
Syntaxwddv $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The six-bit immediate value is used to select operating mode, as follows:

BitsShort nameDescription
.XXXXXreservedReserved and must be zero
X.....indirectIs rhs operand ($rC) indirect or not

$of is cleared.

If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.

Panic if:

  • Reserved bits of the immediate are set
  • The memory range MEM[$rA, 16] does not pass ownership check
  • $rB + 16 overflows or > VM_MAX_RAM
  • indirect == 1 and $rC + 16 overflows or > VM_MAX_RAM

WQDV: 256-bit integer division

DescriptionDivide a 256-bit integer by another.
Operationb = mem[$rB,32];
c = indirect?mem[$rC,32]:$rC;
mem[$rA,32] = b / c;
Syntaxwqdv $rA, $rB, $rC, imm
Encoding0x00 rA rB rC i
Notes

The immediate value is interpreted identically to WDDV.

$of is cleared.

If the rhs operand is zero, MEM[$rA, 32] is cleared and $err is set to true. Otherwise, $err is cleared.

Panic if:

  • Reserved bits of the immediate are set
  • The memory range MEM[$rA, 32] does not pass ownership check
  • $rB + 32 overflows or > VM_MAX_RAM
  • indirect == 1 and $rC + 32 overflows or > VM_MAX_RAM

WDMD: 128-bit integer fused multiply-divide

DescriptionCombined multiply-divide of 128-bit integers with arbitrary precision.
Operationb=mem[$rB,16];
c=mem[$rC,16];
d=mem[$rD,16];
mem[$rA,16]=(b * c) / d;
Syntaxwddv $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
NotesDivision by zero is treated as division by 1 << 128 instead.

If the divisor MEM[$rA, 16] is zero, then instead the value is divided by 1 << 128. This returns the higher half of the 256-bit multiplication result.

If the result of after the division is larger than operand size, $of is set to one. Otherwise, $of is cleared.

$err is cleared.

Panic if:

  • The memory range MEM[$rA, 16] does not pass ownership check
  • $rB + 16 overflows or > VM_MAX_RAM
  • $rC + 16 overflows or > VM_MAX_RAM
  • $rD + 16 overflows or > VM_MAX_RAM

WQMD: 256-bit integer fused multiply-divide

DescriptionCombined multiply-divide of 256-bit integers with arbitrary precision.
Operationb=mem[$rB,32];
c=mem[$rC,32];
d=mem[$rD,32];
mem[$rA,32]=(b * c) / d;
Syntaxwqdv $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
NotesDivision by zero is treated as division by 1 << 256 instead.

If the divisor MEM[$rA, 32] is zero, then instead the value is divided by 1 << 256. This returns the higher half of the 512-bit multiplication result.

If the result of after the division is larger than operand size, $of is set to one. Otherwise, $of is cleared.

$err is cleared.

Panic if:

  • The memory range MEM[$rA, 32] does not pass ownership check
  • $rB + 32 overflows or > VM_MAX_RAM
  • $rC + 32 overflows or > VM_MAX_RAM
  • $rD + 32 overflows or > VM_MAX_RAM

WDAM: Modular 128-bit integer addition

DescriptionAdd two 128-bit integers and compute modulo remainder with arbitrary precision.
Operationb=mem[$rB,16];
c=mem[$rC,16];
d=mem[$rD,16];
mem[$rA,16] = (b+c)%d;
Syntaxwdam $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

$of is cleared.

If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.

Panic if:

  • The memory range MEM[$rA, 16] does not pass ownership check
  • $rB + 16 overflows or > VM_MAX_RAM
  • $rC + 16 overflows or > VM_MAX_RAM
  • $rD + 16 overflows or > VM_MAX_RAM

WQAM: Modular 256-bit integer addition

DescriptionAdd two 256-bit integers and compute modulo remainder with arbitrary precision.
Operationb=mem[$rB,32];
c=mem[$rC,32];
d=mem[$rD,32];
mem[$rA,32] = (b+c)%d;
Syntaxwdam $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

$of is cleared.

If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.

Panic if:

  • The memory range MEM[$rA, 32] does not pass ownership check
  • $rB + 32 overflows or > VM_MAX_RAM
  • $rC + 32 overflows or > VM_MAX_RAM
  • $rD + 32 overflows or > VM_MAX_RAM

WDMM: Modular 128-bit integer multiplication

DescriptionMultiply two 128-bit integers and compute modulo remainder with arbitrary precision.
Operationb=mem[$rB,16];
c=mem[$rC,16];
d=mem[$rD,16];
mem[$rA,16] = (b*c)%d;
Syntaxwdmm $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

$of is cleared.

If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.

Panic if:

  • The memory range MEM[$rA, 16] does not pass ownership check
  • $rB + 16 overflows or > VM_MAX_RAM
  • $rC + 16 overflows or > VM_MAX_RAM
  • $rD + 16 overflows or > VM_MAX_RAM

WQMM: Modular 256-bit integer multiplication

DescriptionMultiply two 256-bit integers and compute modulo remainder with arbitrary precision.
Operationb=mem[$rB,32];
c=mem[$rC,32];
d=mem[$rD,32];
mem[$rA,32] = (b*c)%d;
Syntaxwqmm $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

$of is cleared.

If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.

Panic if:

  • The memory range MEM[$rA, 32] does not pass ownership check
  • $rB + 32 overflows or > VM_MAX_RAM
  • $rC + 32 overflows or > VM_MAX_RAM
  • $rD + 32 overflows or > VM_MAX_RAM

XOR: XOR

DescriptionBitwise XORs two registers.
Operation$rA = $rB ^ $rC;
Syntaxxor $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

$of and $err are cleared.

XORI: XOR immediate

DescriptionBitwise XORs a register and an immediate value.
Operation$rA = $rB ^ imm;
Syntaxxori $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

$of and $err are cleared.

Control Flow Instructions

JMP: Jump

DescriptionJumps to the code instruction offset by a register.
Operation$pc = $is + $rA * 4;
Syntaxjmp $rA
Encoding0x00 rA - - -
Notes

Panic if:

  • $is + $rA * 4 > VM_MAX_RAM - 1

JI: Jump immediate

DescriptionJumps to the code instruction offset by imm.
Operation$pc = $is + imm * 4;
Syntaxji imm
Encoding0x00 i i i i
Notes

Panic if:

  • $is + imm * 4 > VM_MAX_RAM - 1

JNE: Jump if not equal

DescriptionJump to the code instruction offset by a register if $rA is not equal to $rB.
Operationif $rA != $rB:
$pc = $is + $rC * 4;
else:
$pc += 4;
Syntaxjne $rA $rB $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $is + $rC * 4 > VM_MAX_RAM - 1 and the jump would be performed (i.e. $rA != $rB)

JNEI: Jump if not equal immediate

DescriptionJump to the code instruction offset by imm if $rA is not equal to $rB.
Operationif $rA != $rB:
$pc = $is + imm * 4;
else:
$pc += 4;
Syntaxjnei $rA $rB imm
Encoding0x00 rA rB i i
Notes

Panic if:

  • $is + imm * 4 > VM_MAX_RAM - 1 and the jump would be performed (i.e. $rA != $rB)

JNZI: Jump if not zero immediate

DescriptionJump to the code instruction offset by imm if $rA is not equal to $zero.
Operationif $rA != $zero:
$pc = $is + imm * 4;
else:
$pc += 4;
Syntaxjnzi $rA imm
Encoding0x00 rA i i i
Notes

Panic if:

  • $is + imm * 4 > VM_MAX_RAM - 1and the jump would be performed (i.e. $rA != $zero)

JMPB: Jump relative backwards

DescriptionJump $rA + imm instructions backwards.
Operation$pc -= ($rA + imm + 1) * 4;
Syntaxjmpb $rA imm
Encoding0x00 rA i i i
Notes

Panic if:

  • $pc - ($rA + imm + 1) * 4 < 0

JMPF: Jump relative forwards

DescriptionJump $rA + imm instructions forwards
Operation$pc += ($rA + imm + 1) * 4;
Syntaxjmpf $rA imm
Encoding0x00 rA i i i
Notes

Panic if:

  • $pc + ($rA + imm + 1) * 4 > VM_MAX_RAM - 1

JNZB: Jump if not zero relative backwards

DescriptionJump $rB + imm instructions backwards if $rA != $zero.
Operationif $rA != $zero:
$pc -= ($rB + imm + 1) * 4;
else:
$pc += 4;
Syntaxjnzb $rA $rB imm
Encoding0x00 rA rB i i
Notes

Panic if:

  • $pc - ($rB + imm + 1) * 4 < 0

JNZF: Jump if not zero relative forwards

DescriptionJump $rB + imm instructions forwards if $rA != $zero.
Operationif $rA != $zero:
$pc += ($rB + imm + 1) * 4;
else:
$pc += 4;
Syntaxjnzf $rA $rB imm
Encoding0x00 rA rB i i
Notes

Panic if:

  • $pc + ($rB + imm + 1) * 4 > VM_MAX_RAM - 1

JNEB: Jump if not equal relative backwards

DescriptionJump $rC + imm instructions backwards if $rA != $rB.
Operationif $rA != $rB:
$pc -= ($rC + imm + 1) * 4;
else:
$pc += 4;
Syntaxjneb $rA $rB $rC imm
Encoding0x00 rA rB rC i
Notes

Panic if:

  • $pc - ($rC + imm + 1) * 4 < 0

JNEF: Jump if not equal relative forwards

DescriptionJump $rC + imm instructions forwards if $rA != $rB.
Operationif $rA != $rB:
$pc += ($rC + imm + 1) * 4;
else:
$pc += 4;
Syntaxjnef $rA $rB $rC imm
Encoding0x00 rA rB rC i
Notes

Panic if:

  • $pc + ($rC + imm + 1) * 4 > VM_MAX_RAM - 1

RET: Return from context

DescriptionReturns from context with value $rA.
Operationreturn($rA);
Syntaxret $rA
Encoding0x00 rA - - -
Notes

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Return
idbyte[32]Contract ID of current context if in an internal context, zero otherwise.
valuint64Value of register $rA.
pcuint64Value of register $pc.
isuint64Value of register $is.

If current context is external, append an additional receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.ScriptResult
resultuint640
gas_useduint64Gas consumed by the script.

If current context is external, cease VM execution and return $rA.

Returns from contract call, popping the call frame. Before popping perform the following operations.

Return the unused forwarded gas to the caller:

  1. $cgas = $cgas + $fp->$cgas (add remaining context gas from previous context to current remaining context gas)

Set the return value:

  1. $ret = $rA
  2. $retl = 0

Then pop the call frame and restore all registers except $ggas, $cgas, $ret, $retl and $hp. Afterwards, set the following registers:

  1. $pc = $pc + 4 (advance program counter from where we called)

Memory Instructions

All these instructions advance the program counter $pc by 4 after performing their operation.

ALOC: Allocate memory

DescriptionAllocate a number of bytes from the heap.
Operation$hp = $hp - $rA;
Syntaxaloc $rA
Encoding0x00 rA - - -
NotesNewly allocated memory is zeroed.

Panic if:

  • $hp - $rA underflows
  • $hp - $rA < $sp

CFE: Extend call frame

DescriptionExtend the current call frame's stack.
Operation$sp = $sp + $rA
Syntaxcfei $rA
Encoding0x00 rA - - -
NotesDoes not initialize memory.

Panic if:

  • $sp + $rA overflows
  • $sp + $rA > $hp

CFEI: Extend call frame immediate

DescriptionExtend the current call frame's stack by an immediate value.
Operation$sp = $sp + imm
Syntaxcfei imm
Encoding0x00 i i i i
NotesDoes not initialize memory.

Panic if:

  • $sp + imm overflows
  • $sp + imm > $hp

CFS: Shrink call frame

DescriptionShrink the current call frame's stack.
Operation$sp = $sp - $rA
Syntaxcfs $rA
Encoding0x00 $rA - - -
NotesDoes not clear memory.

Panic if:

  • $sp - $rA underflows
  • $sp - $rA < $ssp

CFSI: Shrink call frame immediate

DescriptionShrink the current call frame's stack by an immediate value.
Operation$sp = $sp - imm
Syntaxcfsi imm
Encoding0x00 i i i i
NotesDoes not clear memory.

Panic if:

  • $sp - imm underflows
  • $sp - imm < $ssp

LB: Load byte

DescriptionA byte is loaded from the specified address offset by imm.
Operation$rA = MEM[$rB + imm, 1];
Syntaxlb $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

LW: Load word

DescriptionA word is loaded from the specified address offset by imm.
Operation$rA = MEM[$rB + (imm * 8), 8];
Syntaxlw $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

  • $rA is a reserved register
  • $rB + (imm * 8) + 8 overflows
  • $rB + (imm * 8) + 8 > VM_MAX_RAM

MCL: Memory clear

DescriptionClear bytes in memory.
OperationMEM[$rA, $rB] = 0;
Syntaxmcl $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

  • $rA + $rB overflows
  • $rA + $rB > VM_MAX_RAM
  • The memory range MEM[$rA, $rB] does not pass ownership check

MCLI: Memory clear immediate

DescriptionClear bytes in memory.
OperationMEM[$rA, imm] = 0;
Syntaxmcli $rA, imm
Encoding0x00 rA i i i
Notes

Panic if:

  • $rA + imm overflows
  • $rA + imm > VM_MAX_RAM
  • The memory range MEM[$rA, imm] does not pass ownership check

MCP: Memory copy

DescriptionCopy bytes in memory.
OperationMEM[$rA, $rC] = MEM[$rB, $rC];
Syntaxmcp $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + $rC overflows
  • $rB + $rC overflows
  • $rA + $rC > VM_MAX_RAM
  • $rB + $rC > VM_MAX_RAM
  • The memory ranges MEM[$rA, $rC] and MEM[$rB, $rC] overlap
  • The memory range MEM[$rA, $rC] does not pass ownership check

MCPI: Memory copy immediate

DescriptionCopy bytes in memory.
OperationMEM[$rA, imm] = MEM[$rB, imm];
Syntaxmcpi $rA, $rB, imm
Encoding0x00 rA rB imm imm
Notes

Panic if:

  • $rA + imm overflows
  • $rB + imm overflows
  • $rA + imm > VM_MAX_RAM
  • $rB + imm > VM_MAX_RAM
  • The memory ranges MEM[$rA, imm] and MEM[$rB, imm] overlap
  • The memory range MEM[$rA, imm] does not pass ownership check

MEQ: Memory equality

DescriptionCompare bytes in memory.
Operation$rA = MEM[$rB, $rD] == MEM[$rC, $rD];
Syntaxmeq $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

Panic if:

  • $rA is a reserved register
  • $rB + $rD overflows
  • $rC + $rD overflows
  • $rB + $rD > VM_MAX_RAM
  • $rC + $rD > VM_MAX_RAM

PSHH: Push a set of high registers to stack

DescriptionPush a set of registers from range 40..64 to the stack in order.
Operationtmp=$sp;
$sp+=popcnt(imm)*8;
MEM[tmp,$sp]=registers[40..64].mask(imm)
Syntaxpshh imm
Encoding0x00 i i i i
NotesThe immediate value is used as a bitmask for selecting the registers.

The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pushes the register 61 followed by register 62.

Panic if:

  • $sp + popcnt(imm)*8 overflows
  • $sp + popcnt(imm)*8 > $hp

PSHL: Push a set of low registers to stack

DescriptionPush a set of registers from range 16..40 to the stack in order.
Operationtmp=$sp;
$sp+=popcnt(imm)*8;
MEM[tmp,$sp]=registers[16..40].mask(imm)
Syntaxpshl imm
Encoding0x00 i i i i
NotesThe immediate value is used as a bitmask for selecting the registers.

The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pushes the register 37 followed by register 38.

Panic if:

  • $sp + popcnt(imm)*8 overflows
  • $sp + popcnt(imm)*8 > $hp

POPH: Pop a set of high registers from stack

DescriptionPop to a set of registers from range 40..64 from the stack.
Operationtmp=$sp-popcnt(imm)*8;
registers[40..64].mask(imm)=MEM[tmp,$sp]
$sp-=tmp;
Syntaxpoph imm
Encoding0x00 i i i i
NotesThe immediate value is used as a bitmask for selecting the registers.

The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pops the register 62 followed by register 61.

Note that the order is reverse from PSHH, so that PSHH a; POPH a returns to the original state.

Panic if:

  • $sp - popcnt(imm)*8 overflows
  • $sp - popcnt(imm)*8 < $ssp

POPL: Pop a set of low registers from stack

DescriptionPop to a set of registers from range 16..40 from the stack.
Operationtmp=$sp-popcnt(imm)*8;
registers[16..40].mask(imm)=MEM[tmp,$sp]
$sp-=tmp;
Syntaxpoph imm
Encoding0x00 i i i i
NotesThe immediate value is used as a bitmask for selecting the registers.

The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pops the register 38 followed by register 37.

Note that the order is reverse from PSHL, so that PSHL a; POPL a returns to the original state.

Panic if:

  • $sp - popcnt(imm)*8 overflows
  • $sp - popcnt(imm)*8 < $ssp

SB: Store byte

DescriptionThe least significant byte of $rB is stored at the address $rA offset by imm.
OperationMEM[$rA + imm, 1] = $rB[7, 1];
Syntaxsb $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

  • $rA + imm + 1 overflows
  • $rA + imm + 1 > VM_MAX_RAM
  • The memory range MEM[$rA + imm, 1] does not pass ownership check

SW: Store word

DescriptionThe value of $rB is stored at the address $rA offset by imm.
OperationMEM[$rA + (imm * 8), 8] = $rB;
Syntaxsw $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Panic if:

  • $rA + (imm * 8) + 8 overflows
  • $rA + (imm * 8) + 8 > VM_MAX_RAM
  • The memory range MEM[$rA + (imm * 8), 8] does not pass ownership check

Contract Instructions

All these instructions advance the program counter $pc by 4 after performing their operation, except for CALL, RETD and RVRT.

BAL: Balance of contract ID

DescriptionSet $rA to the balance of asset ID at $rB for contract with ID at $rC.
Operation$rA = balance(MEM[$rB, 32], MEM[$rC, 32]);
Syntaxbal $rA, $rB, $rC
Encoding0x00 rA rB rC -
EffectsBalance tree read
Notes

Where helper balance(asset_id: byte[32], contract_id: byte[32]) -> uint64 returns the current balance of asset_id of contract with ID contract_id.

Panic if:

  • $rA is a reserved register
  • $rB + 32 overflows
  • $rB + 32 > VM_MAX_RAM
  • $rC + 32 overflows
  • $rC + 32 > VM_MAX_RAM
  • Contract with ID MEM[$rC, 32] is not in tx.inputs

BHEI: Block height

DescriptionGet Fuel block height.
Operation$rA = blockheight();
Syntaxbhei $rA
Encoding0x00 rA - - -
Notes

Panic if:

BHSH: Block hash

DescriptionGet block header hash.
OperationMEM[$rA, 32] = blockhash($rB);
Syntaxbhsh $rA $rB
Encoding0x00 rA rB - -
Notes

Panic if:

  • $rA + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • The memory range MEM[$rA, 32] does not pass ownership check

Block header hashes for blocks with height greater than or equal to current block height are zero (0x00**32).

BURN: Burn existing coins

DescriptionBurn $rA coins of the $rB ID from the current contract.
Operationburn($rA, $rB);
Syntaxburn $rA $rB
Encoding0x00 rA rB - -
Notes$rB is a pointer to a 32 byte ID in memory.

The asset ID is constructed using the asset ID construction method.

Panic if:

  • $rB + 32 > VM_MAX_RAM
  • Balance of asset ID from constructAssetID(MEM[$fp, 32], MEM[$rB, 32]) of output with contract ID MEM[$fp, 32] minus $rA underflows
  • $fp == 0 (in the script context)

For output with contract ID MEM[$fp, 32], decrease balance of asset ID constructAssetID(MEM[$fp, 32], MEM[$rB, 32]) by $rA.

This modifies the balanceRoot field of the appropriate output.

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Burn
sub_idbyte[32]Asset sub identifier MEM[$rB, $rB + 32].
contract_idbyte[32]Contract ID of the current context.
valuint64Value of register $rA.
pcuint64Value of register $pc.
isuint64Value of register $is.

CALL: Call contract

DescriptionCall contract.
Operation
Syntaxcall $rA $rB $rC $rD
Encoding0x00 rA rB rC rD
EffectsExternal call
Notes

There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.

Panic if:

  • $rA + 32 overflows
  • $rC + 32 overflows
  • Contract with ID MEM[$rA, 32] is not in tx.inputs
  • Reading past MEM[VM_MAX_RAM - 1]
  • In an external context, if $rB > MEM[balanceOfStart(MEM[$rC, 32]), 8]
  • In an internal context, if $rB is greater than the balance of asset ID MEM[$rC, 32] of output with contract ID MEM[$fp, 32]

Register $rA is a memory address from which the following fields are set (word-aligned):

bytestypevaluedescription
32byte[32]toContract ID to call.
8byte[8]param1First parameter.
8byte[8]param2Second parameter.

$rB is the amount of coins to forward. $rC points to the 32-byte asset ID of the coins to forward. $rD is the amount of gas to forward. If it is set to an amount greater than the available gas, all available gas is forwarded.

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Call
frombyte[32]Contract ID of current context if in an internal context, zero otherwise.
tobyte[32]Contract ID of called contract.
amountuint64Amount of coins to forward, i.e. $rB.
asset_idbyte[32]Asset ID of coins to forward, i.e. MEM[$rC, 32].
gasuint64Gas to forward, i.e. min($rD, $cgas).
param1uint64First parameter.
param2uint64Second parameter.
pcuint64Value of register $pc.
isuint64Value of register $is.

For output with contract ID MEM[$rA, 32], increase balance of asset ID MEM[$rC, 32] by $rB. In an external context, decrease MEM[balanceOfStart(MEM[$rC, 32]), 8] by $rB. In an internal context, decrease asset ID MEM[$rC, 32] balance of output with contract ID MEM[$fp, 32] by $rB.

A call frame is pushed at $sp. In addition to filling in the values of the call frame, the following registers are set:

  1. $fp = $sp (on top of the previous call frame is the beginning of this call frame)
  2. Set $ssp and $sp to the start of the writable stack area of the call frame.
  3. Set $pc and $is to the starting address of the code.
  4. $bal = $rB (forward coins)
  5. $cgas = $rD or all available gas (forward gas)

This modifies the balanceRoot field of the appropriate output(s).

CB: Coinbase contract id

DescriptionGet the coinbase contract id associated with the block proposer.
OperationMEM[$rA, 32] = coinbase();
Syntaxcb $rA
Encoding0x00 rA - - -
Notes

Panic if:

  • $rA + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • The memory range MEM[$rA, 32] does not pass ownership check

CCP: Code copy

DescriptionCopy $rD bytes of code starting at $rC for contract with ID equal to the 32 bytes in memory starting at $rB into memory starting at $rA.
OperationMEM[$rA, $rD] = code($rB, $rC, $rD);
Syntaxccp $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
NotesIf $rD is greater than the code size, zero bytes are filled in.

This is used only for reading and inspecting code of other contracts. Use LDC to load code for executing.

Panic if:

  • $rA + $rD overflows
  • $rB + 32 overflows
  • $rA + $rD > VM_MAX_RAM
  • $rB + 32 > VM_MAX_RAM
  • The memory range MEM[$rA, $rD] does not pass ownership check
  • Contract with ID MEM[$rB, 32] is not in tx.inputs

CROO: Code Merkle root

DescriptionSet the 32 bytes in memory starting at $rA to the code root for contract with ID equal to the 32 bytes in memory starting at $rB.
OperationMEM[$rA, 32] = coderoot(MEM[$rB, 32]);
Syntaxcroo $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

  • $rA + 32 overflows
  • $rB + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB + 32 > VM_MAX_RAM
  • The memory range MEM[$rA, 32] does not pass ownership check
  • Contract with ID MEM[$rB, 32] is not in tx.inputs

Code root computation is defined here.

CSIZ: Code size

DescriptionSet $rA to the size of the code for contract with ID equal to the 32 bytes in memory starting at $rB.
Operation$rA = codesize(MEM[$rB, 32]);
Syntaxcsiz $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

  • $rA is a reserved register
  • $rB + 32 overflows
  • $rB + 32 > VM_MAX_RAM
  • Contract with ID MEM[$rB, 32] is not in tx.inputs

LDC: Load code from an external contract

DescriptionCopy $rC bytes of code starting at $rB for contract with ID equal to the 32 bytes in memory starting at $rA into memory starting at $ssp.
OperationMEM[$ssp, $rC] = code($rA, $rB, $rC);
Syntaxldc $rA, $rB, $rC
Encoding0x00 rA rB rC -
NotesIf $rC is greater than the code size, zero bytes are filled in.

Panic if:

  • $ssp + $rC overflows
  • $rA + 32 overflows
  • $ssp + $rC > VM_MAX_RAM
  • $rA + 32 > VM_MAX_RAM
  • $ssp + $rC >= $hp
  • $rC > CONTRACT_MAX_SIZE
  • Contract with ID MEM[$rA, 32] is not in tx.inputs

Increment $fp->codesize, $ssp by $rC padded to word alignment. Then set $sp to $ssp.

This instruction can be used to concatenate the code of multiple contracts together. It can only be used when the stack area of the call frame is unused (i.e. prior to being used).

LOG: Log event

DescriptionLog an event. This is a no-op.
Operationlog($rA, $rB, $rC, $rD);
Syntaxlog $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Log
idbyte[32]Contract ID of current context if in an internal context, zero otherwise.
val0uint64Value of register $rA.
val1uint64Value of register $rB.
val2uint64Value of register $rC.
val3uint64Value of register $rD.
pcuint64Value of register $pc.
isuint64Value of register $is.

LOGD: Log data event

DescriptionLog an event. This is a no-op.
Operationlogd($rA, $rB, $rC, $rD);
Syntaxlogd $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
Notes

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.LogData
idbyte[32]Contract ID of current context if in an internal context, zero otherwise.
val0uint64Value of register $rA.
val1uint64Value of register $rB.
ptruint64Value of register $rC.
lenuint64Value of register $rD.
digestbyte[32]Hash of MEM[$rC, $rD].
pcuint64Value of register $pc.
isuint64Value of register $is.

Logs the memory range MEM[$rC, $rD].

Panics if:

  • $rC + $rD overflows
  • $rA + $rD > VM_MAX_RAM

MINT: Mint new coins

DescriptionMint $rA coins of the $rB ID from the current contract.
Operationmint($rA, $rB);
Syntaxmint $rA $rB
Encoding0x00 rA rB - -
Notes$rB is a pointer to a 32 byte ID in memory

The asset ID will be constructed using the asset ID construction method.

Panic if:

  • $rB + 32 > VM_MAX_RAM
  • Balance of asset ID constructAssetID(MEM[$fp, 32], MEM[$rB]) of output with contract ID MEM[$fp, 32] plus $rA overflows
  • $fp == 0 (in the script context)

For output with contract ID MEM[$fp, 32], increase balance of asset ID constructAssetID(MEM[$fp, 32], MEM[$rB]) by $rA.

This modifies the balanceRoot field of the appropriate output.

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Mint
sub_idbyte[32]Asset sub identifier MEM[$rB, $rB + 32].
contract_idbyte[32]Contract ID of the current context.
valuint64Value of register $rA.
pcuint64Value of register $pc.
isuint64Value of register $is.

RETD: Return from context with data

DescriptionReturns from context with value MEM[$rA, $rB].
Operationreturndata($rA, $rB);
Syntaxretd $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

  • $rA + $rB overflows
  • $rA + $rB > VM_MAX_RAM

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.ReturnData
idbyte[32]Contract ID of current context if in an internal context, zero otherwise.
ptruint64Value of register $rA.
lenuint64Value of register $rB.
digestbyte[32]Hash of MEM[$rA, $rB].
pcuint64Value of register $pc.
isuint64Value of register $is.

If current context is a script, append an additional receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.ScriptResult
resultuint640
gas_useduint64Gas consumed by the script.

If current context is external, cease VM execution and return MEM[$rA, $rB].

Returns from contract call, popping the call frame. Before popping, perform the following operations.

Return the unused forwarded gas to the caller:

  1. $cgas = $cgas + $fp->$cgas (add remaining context gas from previous context to current remaining context gas)

Set the return value:

  1. $ret = $rA
  2. $retl = $rB

Then pop the call frame and restore all registers except $ggas, $cgas, $ret, $retl and $hp. Afterwards, set the following registers:

  1. $pc = $pc + 4 (advance program counter from where we called)

RVRT: Revert

DescriptionHalt execution, reverting state changes and returning value in $rA.
Operationrevert($rA);
Syntaxrvrt $rA
Encoding0x00 rA - - -
Notes

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Revert
idbyte[32]Contract ID of current context if in an internal context, zero otherwise.
valuint64Value of register $rA.
pcuint64Value of register $pc.
isuint64Value of register $is.

Then append an additional receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.ScriptResult
resultuint641
gas_useduint64Gas consumed by the script.

Cease VM execution and revert script effects. After a revert:

  1. All OutputContract outputs will have the same balanceRoot and stateRoot as on initialization.
  2. All OutputVariable outputs will have to, amount, and asset_id of zero.

SMO: Send message out

DescriptionSend a message to recipient address MEM[$rA, 32] from the MEM[$fp, 32] sender with message data MEM[$rB, $rC] and the $rD amount of base asset coins.
Operationoutputmessage(MEM[$fp, 32], MEM[$rA, 32], MEM[$rB, $rC], $rD);
Syntaxsmo $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
EffectsOutput message
Notes

There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.

Panic if:

  • $rA + 32 overflows
  • $rB + $rC overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB + $rC > VM_MAX_RAM
  • $rC > MESSAGE_MAX_DATA_SIZE
  • In an external context, if $rD > MEM[balanceOfStart(0), 8]
  • In an internal context, if $rD is greater than the balance of asset ID 0 of output with contract ID MEM[$fp, 32]

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.MessageOut
senderbyte[32]The address of the message sender: MEM[$fp, 32].
recipientbyte[32]The address of the message recipient: MEM[$rA, 32].
amountuint64Amount of base asset coins sent with message: $rD.
noncebyte[32]The message nonce as described here.
lenuint64Length of message data, in bytes: $rC.
digestbyte[32]Hash of MEM[$rB, $rC].

In an external context, decrease MEM[balanceOfStart(0), 8] by $rD. In an internal context, decrease asset ID 0 balance of output with contract ID MEM[$fp, 32] by $rD. This modifies the balanceRoot field of the appropriate contract that had its' funds deducted.

SCWQ: State clear sequential 32 byte slots

DescriptionA sequential series of 32 bytes is cleared from the current contract's state.
OperationSTATE[MEM[$rA, 32], 32 * $rC] = None;
Syntaxscwq $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB is a reserved register
  • $fp == 0 (in the script context)

Register $rB will be set to false if any storage slot in the requested range was already unset (default) and true if all the slots were set.

SRW: State read word

DescriptionA word is read from the current contract's state.
Operation$rA = STATE[MEM[$rC, 32]][0, 8];
Syntaxsrw $rA, $rB, $rC
Encoding0x00 rA rB rC -
EffectsStorage read
NotesReturns zero if the state element does not exist.

Panic if:

Register $rB will be set to false if the requested slot is unset (default) and true if it's set.

SRWQ: State read sequential 32 byte slots

DescriptionA sequential series of 32 bytes is read from the current contract's state.
OperationMEM[$rA, 32 * rD] = STATE[MEM[$rC, 32], 32 * rD];
Syntaxsrwq $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
EffectsStorage read
NotesReturns zero if the state element does not exist.

Panic if:

  • $rA + 32 * rD overflows
  • $rA + 32 * rD > VM_MAX_RAM
  • $rB is a reserved register
  • $rC + 32 * rD overflows
  • $rC + 32 * rD > VM_MAX_RAM
  • The memory range MEM[$rA, 32 * rD] does not pass ownership check
  • $fp == 0 (in the script context)

Register $rB will be set to false if any storage slot in the requested range is unset (default) and true if all the slots are set.

SWW: State write word

DescriptionA word is written to the current contract's state.
OperationSTATE[MEM[$rA, 32]][0, 8] = $rC;
STATE[MEM[$rA, 32]][8, 24] = 0;
Syntaxsww $rA $rB $rC
Encoding0x00 rA rB rC -
EffectsStorage write
NotesAdditional gas is charged when a new storage slot is created.

Panic if:

  • $rA + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB is a reserved register
  • $fp == 0 (in the script context)

The last 24 bytes of STATE[MEM[$rA, 32]] are set to 0. Register $rB will be set to the number of new slots written, i.e. 1 if the slot was previously unset, and 0 if it already contained a value.

SWWQ: State write sequential 32 byte slots

DescriptionA sequential series of 32 bytes is written to the current contract's state.
OperationSTATE[MEM[$rA, 32], 32 * $rD] = MEM[$rC, 32 * $rD];
Syntaxswwq $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
EffectsStorage write
NotesAdditional gas is charged when for each new storage slot created.

Panic if:

  • $rA + 32 overflows
  • $rB is a reserved register
  • $rC + 32 * $rD overflows
  • $rA + 32 > VM_MAX_RAM
  • $rC + 32 * $rD > VM_MAX_RAM
  • $fp == 0 (in the script context)

Register $rB will be set to the number of storage slots that were previously unset, and were set by this operation.

TIME: Timestamp at height

DescriptionGet timestamp of block at given height.
Operation$rA = time($rB);
Syntaxtime $rA, $rB
Encoding0x00 rA rB - -
Notes

Panic if:

Gets the timestamp of the block at height $rB. Time is in TAI64 format.

TR: Transfer coins to contract

DescriptionTransfer $rB coins with asset ID at $rC to contract with ID at $rA.
Operationtransfer(MEM[$rA, 32], $rB, MEM[$rC, 32]);
Syntaxtr $rA, $rB, $rC
Encoding0x00 rA rB rC -
EffectsBalance tree read, balance tree write
Notes

There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.

Panic if:

  • $rA + 32 overflows
  • $rC + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • $rC + 32 > VM_MAX_RAM
  • Contract with ID MEM[$rA, 32] is not in tx.inputs
  • In an external context, if $rB > MEM[balanceOfStart(MEM[$rC, 32]), 8]
  • In an internal context, if $rB is greater than the balance of asset ID MEM[$rC, 32] of output with contract ID MEM[$fp, 32]
  • $rB == 0

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.Transfer
frombyte[32]Contract ID of current context if in an internal context, zero otherwise.
tobyte[32]Contract ID of contract to transfer coins to.
amountuint64Amount of coins transferred.
asset_idbyte[32]asset ID of coins transferred.
pcuint64Value of register $pc.
isuint64Value of register $is.

For output with contract ID MEM[$rA, 32], increase balance of asset ID MEM[$rC, 32] by $rB. In an external context, decrease MEM[balanceOfStart(MEM[$rC, 32]), 8] by $rB. In an internal context, decrease asset ID MEM[$rC, 32] balance of output with contract ID MEM[$fp, 32] by $rB.

This modifies the balanceRoot field of the appropriate output(s).

TRO: Transfer coins to output

DescriptionTransfer $rC coins with asset ID at $rD to address at $rA, with output $rB.
Operationtransferout(MEM[$rA, 32], $rB, $rC, MEM[$rD, 32]);
Syntaxtro $rA, $rB, $rC, $rD
Encoding0x00 rA rB rC rD
EffectsBalance tree read, balance tree write
Notes

There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.

Panic if:

  • $rA + 32 overflows
  • $rD + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • $rD + 32 > VM_MAX_RAM
  • $rB > tx.outputsCount
  • In an external context, if $rC > MEM[balanceOfStart(MEM[$rD, 32]), 8]
  • In an internal context, if $rC is greater than the balance of asset ID MEM[$rD, 32] of output with contract ID MEM[$fp, 32]
  • $rC == 0
  • tx.outputs[$rB].type != OutputType.Variable
  • tx.outputs[$rB].amount != 0

Append a receipt to the list of receipts:

nametypedescription
typeReceiptTypeReceiptType.TransferOut
frombyte[32]Contract ID of current context if in an internal context, zero otherwise.
tobyte[32]Address to transfer coins to.
amountuint64Amount of coins transferred.
asset_idbyte[32]asset ID of coins transferred.
pcuint64Value of register $pc.
isuint64Value of register $is.

In an external context, decrease MEM[balanceOfStart(MEM[$rD, 32]), 8] by $rC. In an internal context, decrease asset ID MEM[$rD, 32] balance of output with contract ID MEM[$fp, 32] by $rC. Then set:

  • tx.outputs[$rB].to = MEM[$rA, 32]
  • tx.outputs[$rB].amount = $rC
  • tx.outputs[$rB].asset_id = MEM[$rD, 32]

This modifies the balanceRoot field of the appropriate output(s).

Cryptographic Instructions

All these instructions advance the program counter $pc by 4 after performing their operation.

ECK1: Secp256k1 signature recovery

DescriptionThe 64-byte public key (x, y) recovered from 64-byte signature starting at $rB on 32-byte message hash starting at $rC.
OperationMEM[$rA, 64] = ecrecover_k1(MEM[$rB, 64], MEM[$rC, 32]);
Syntaxeck1 $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + 64 overflows
  • $rB + 64 overflows
  • $rC + 32 overflows
  • $rA + 64 > VM_MAX_RAM
  • $rB + 64 > VM_MAX_RAM
  • $rC + 32 > VM_MAX_RAM
  • The memory range MEM[$rA, 64] does not pass ownership check

Signatures and signature verification are specified here.

If the signature cannot be verified, MEM[$rA, 64] is set to 0 and $err is set to 1, otherwise $err is cleared.

To get the address from the public key, hash the public key with SHA-2-256.

ECR1: Secp256r1 signature recovery

DescriptionThe 64-byte public key (x, y) recovered from 64-byte signature starting at $rB on 32-byte message hash starting at $rC.
OperationMEM[$rA, 64] = ecrecover_r1(MEM[$rB, 64], MEM[$rC, 32]);
Syntaxecr1 $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + 64 overflows
  • $rB + 64 overflows
  • $rC + 32 overflows
  • $rA + 64 > VM_MAX_RAM
  • $rB + 64 > VM_MAX_RAM
  • $rC + 32 > VM_MAX_RAM
  • The memory range MEM[$rA, 64] does not pass ownership check

Signatures and signature verification are specified here.

If the signature cannot be verified, MEM[$rA, 64] is set to 0 and $err is set to 1, otherwise $err is cleared.

To get the address from the public key, hash the public key with SHA-2-256.

ED19: EdDSA curve25519 verification

DescriptionVerification recovered from 32-byte public key starting at $rA and 64-byte signature starting at $rB on 32-byte message hash starting at $rC.
Operationed19verify(MEM[$rA, 32], MEM[$rB, 64], MEM[$rC, 32]);
Syntaxed19 $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + 32 overflows
  • $rB + 64 overflows
  • $rC + 32 overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB + 64 > VM_MAX_RAM
  • $rC + 32 > VM_MAX_RAM

Verification are specified here.

If there is an error in verification, $err is set to 1, otherwise $err is cleared.

K256: keccak-256

DescriptionThe keccak-256 hash of $rC bytes starting at $rB.
OperationMEM[$rA, 32] = keccak256(MEM[$rB, $rC]);
Syntaxk256 $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + 32 overflows
  • $rB + $rC overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB + $rC > VM_MAX_RAM
  • The memory range MEM[$rA, 32] does not pass ownership check

S256: SHA-2-256

DescriptionThe SHA-2-256 hash of $rC bytes starting at $rB.
OperationMEM[$rA, 32] = sha256(MEM[$rB, $rC]);
Syntaxs256 $rA, $rB, $rC
Encoding0x00 rA rB rC -
Notes

Panic if:

  • $rA + 32 overflows
  • $rB + $rC overflows
  • $rA + 32 > VM_MAX_RAM
  • $rB + $rC > VM_MAX_RAM
  • The memory range MEM[$rA, 32] does not pass ownership check

Other Instructions

All these instructions advance the program counter $pc by 4 after performing their operation.

ECAL: Call external function

DescriptionCall an external function that has full access to the VM state.
Operationexternal(&mut vm, $rA, $rB, $rC, $rD)
Syntaxecal $rA $rB $rC $rD
Encoding0x00 rA rB rC rD
NotesDoes nothing by default, but the VM user can define this to do anything.

This function provides an escape hatch from the VM, similar to ecall instruction of RISC-V. The suggested convention is to use $rA for "system call number", i.e. identifying the procedure to call, but all arguments can be used freely. The operation can modify the VM state freely, including writing to registers and memory. Again, the suggested convention is to use $rA for the return value and $err for any possible errors. However, these conventions can be ignored when necessary.

Panic if:

  • The external function panics.

FLAG: Set flags

DescriptionSet $flag to $rA.
Operation$flag = $rA;
Syntaxflag $rA
Encoding0x00 rA - - -
Notes

Panic if:

  • Any reserved flags are set

GM: Get metadata

DescriptionGet metadata from memory.
OperationVaries (see below).
Syntaxgm $rA, imm
Encoding0x00 rA imm imm imm
Notes

Read metadata from memory. A convenience instruction to avoid manually extracting metadata.

namevaluedescription
GM_IS_CALLER_EXTERNAL0x00001Get if caller is external.
GM_GET_CALLER0x00002Get caller's contract ID.
GM_GET_VERIFYING_PREDICATE0x00003Get index of current predicate.
GM_GET_CHAIN_ID0x00004Get the value of CHAIN_ID
GM_TX_START0x00005Transaction start memory address
GM_BASE_ASSET_ID0x00006Base asset ID

If imm == GM_IS_CALLER_EXTERNAL:

Panic if:

  • $fp == 0 (in an external context)

Set $rA to true if parent is an external context, false otherwise.

If imm == GM_GET_CALLER:

Panic if:

  • $fp == 0 (in an external context)
  • $fp->$fp == 0 (if parent context is external)

Set $rA to $fp->$fp (i.e. $rA will point to the previous call frame's contract ID).

If imm == GM_GET_VERIFYING_PREDICATE:

Panic if:

  • not in a predicate context

Set $rA to the index of the currently-verifying predicate.

GTF: Get transaction fields

DescriptionGet transaction fields.
OperationVaries (see below).
Syntaxgtf $rA, $rB, imm
Encoding0x00 rA rB i i
Notes

Get fields from the transaction.

nameimmset $rA to
GTF_TYPE0x001tx.type
GTF_SCRIPT_GAS_LIMIT0x002tx.scriptGasLimit
GTF_SCRIPT_SCRIPT_LENGTH0x003tx.scriptLength
GTF_SCRIPT_SCRIPT_DATA_LENGTH0x004tx.scriptDataLength
GTF_SCRIPT_INPUTS_COUNT0x005tx.inputsCount
GTF_SCRIPT_OUTPUTS_COUNT0x006tx.outputsCount
GTF_SCRIPT_WITNESSES_COUNT0x007tx.witnessesCount
GTF_SCRIPT_SCRIPT0x009Memory address of tx.script
GTF_SCRIPT_SCRIPT_DATA0x00AMemory address of tx.scriptData
GTF_SCRIPT_INPUT_AT_INDEX0x00BMemory address of tx.inputs[$rB]
GTF_SCRIPT_OUTPUT_AT_INDEX0x00CMemory address of t.outputs[$rB]
GTF_SCRIPT_WITNESS_AT_INDEX0x00DMemory address of tx.witnesses[$rB]
GTF_TX_LENGTH0x00ELength of raw transaction types in memory
GTF_CREATE_BYTECODE_WITNESS_INDEX0x101tx.bytecodeWitnessIndex
GTF_CREATE_STORAGE_SLOTS_COUNT0x102tx.storageSlotsCount
GTF_CREATE_INPUTS_COUNT0x103tx.inputsCount
GTF_CREATE_OUTPUTS_COUNT0x104tx.outputsCount
GTF_CREATE_WITNESSES_COUNT0x105tx.witnessesCount
GTF_CREATE_SALT0x106Memory address of tx.salt
GTF_CREATE_STORAGE_SLOT_AT_INDEX0x107Memory address of tx.storageSlots[$rB]
GTF_CREATE_INPUT_AT_INDEX0x108Memory address of tx.inputs[$rB]
GTF_CREATE_OUTPUT_AT_INDEX0x109Memory address of t.outputs[$rB]
GTF_CREATE_WITNESS_AT_INDEX0x10AMemory address of tx.witnesses[$rB]
GTF_INPUT_TYPE0x200tx.inputs[$rB].type
GTF_INPUT_COIN_TX_ID0x201Memory address of tx.inputs[$rB].txID
GTF_INPUT_COIN_OUTPUT_INDEX0x202tx.inputs[$rB].outputIndex
GTF_INPUT_COIN_OWNER0x203Memory address of tx.inputs[$rB].owner
GTF_INPUT_COIN_AMOUNT0x204tx.inputs[$rB].amount
GTF_INPUT_COIN_ASSET_ID0x205Memory address of tx.inputs[$rB].asset_id
GTF_INPUT_COIN_WITNESS_INDEX0x207tx.inputs[$rB].witnessIndex
GTF_INPUT_COIN_PREDICATE_LENGTH0x209tx.inputs[$rB].predicateLength
GTF_INPUT_COIN_PREDICATE_DATA_LENGTH0x20Atx.inputs[$rB].predicateDataLength
GTF_INPUT_COIN_PREDICATE0x20BMemory address of tx.inputs[$rB].predicate
GTF_INPUT_COIN_PREDICATE_DATA0x20CMemory address of tx.inputs[$rB].predicateData
GTF_INPUT_COIN_PREDICATE_GAS_USED0x20Dtx.inputs[$rB].predicateGasUsed
GTF_INPUT_CONTRACT_CONTRACT_ID0x225Memory address of tx.inputs[$rB].contractID
GTF_INPUT_MESSAGE_SENDER0x240Memory address of tx.inputs[$rB].sender
GTF_INPUT_MESSAGE_RECIPIENT0x241Memory address of tx.inputs[$rB].recipient
GTF_INPUT_MESSAGE_AMOUNT0x242tx.inputs[$rB].amount
GTF_INPUT_MESSAGE_NONCE0x243Memory address of tx.inputs[$rB].nonce
GTF_INPUT_MESSAGE_WITNESS_INDEX0x244tx.inputs[$rB].witnessIndex
GTF_INPUT_MESSAGE_DATA_LENGTH0x245tx.inputs[$rB].dataLength
GTF_INPUT_MESSAGE_PREDICATE_LENGTH0x246tx.inputs[$rB].predicateLength
GTF_INPUT_MESSAGE_PREDICATE_DATA_LENGTH0x247tx.inputs[$rB].predicateDataLength
GTF_INPUT_MESSAGE_DATA0x248Memory address of tx.inputs[$rB].data
GTF_INPUT_MESSAGE_PREDICATE0x249Memory address of tx.inputs[$rB].predicate
GTF_INPUT_MESSAGE_PREDICATE_DATA0x24AMemory address of tx.inputs[$rB].predicateData
GTF_INPUT_MESSAGE_PREDICATE_GAS_USED0x24Btx.inputs[$rB].predicateGasUsed
GTF_OUTPUT_TYPE0x300tx.outputs[$rB].type
GTF_OUTPUT_COIN_TO0x301Memory address of tx.outputs[$rB].to
GTF_OUTPUT_COIN_AMOUNT0x302tx.outputs[$rB].amount
GTF_OUTPUT_COIN_ASSET_ID0x303Memory address of tx.outputs[$rB].asset_id
GTF_OUTPUT_CONTRACT_INPUT_INDEX0x304tx.outputs[$rB].inputIndex
GTF_OUTPUT_CONTRACT_BALANCE_ROOT0x305Memory address of tx.outputs[$rB].balanceRoot
GTF_OUTPUT_CONTRACT_STATE_ROOT0x306Memory address of tx.outputs[$rB].stateRoot
GTF_OUTPUT_CONTRACT_CREATED_CONTRACT_ID0x307Memory address of tx.outputs[$rB].contractID
GTF_OUTPUT_CONTRACT_CREATED_STATE_ROOT0x308Memory address of tx.outputs[$rB].stateRoot
GTF_WITNESS_DATA_LENGTH0x400tx.witnesses[$rB].dataLength
GTF_WITNESS_DATA0x401Memory address of tx.witnesses[$rB].data
GTF_POLICY_TYPES0x500tx.policies.policyTypes
GTF_POLICY_TIP0x501tx.policies[0x00].tip
GTF_POLICY_WITNESS_LIMIT0x502tx.policies[count_ones(0b11 & tx.policyTypes) - 1].witnessLimit
GTF_POLICY_MATURITY0x503tx.policies[count_ones(0b111 & tx.policyTypes) - 1].maturity
GTF_POLICY_MAX_FEE0x504tx.policies[count_ones(0b1111 & tx.policyTypes) - 1].maxFee

Panic if:

  • $rA is a reserved register
  • imm is not one of the values listed above
  • The value of $rB results in an out of bounds access for variable-length fields
  • The input or output type does not match (OutputChange and OutputVariable count as OutputCoin)
  • The requested policy type is not set for this transaction.

For fixed-length fields, the value of $rB is ignored.

Networks

Specifications for network-specific components of the protocol.

PoA Network

Consensus Header

Wraps the application header.

nametypedescription
prevRootbyte[32]Merkle root of all previous consensus header hashes (i.e. not including this block).
heightuint32Height of this block.
timestampuint64Time this block was created, in TAI64 format.
applicationHashbyte[32]Hash of serialized application header for this block.

Consensus for the consensus header is a single signature from the authority over the hash of the serialized consensus header.

Since the system is secure under the assumption the authority is honest, there is no need for committing to the authority signatures in the header.

Testing

Test suites for verifying the correctness of a Fuel implementation.

Sparse Merkle Tree Test Specifications

Version

0.1.1

Last updated 2022/07/11

Abstract

This document outlines a test suite specification that can be used to verify the correctness of a Sparse Merkle Tree's outputs. The scope of this document covers only Sparse Merkle Tree (SMT) implementations that are compliant with Celestia Sparse Merkle Tree Specification. The goal of this document is to equip SMT library developers with a supplemental indicator of correctness. Libraries implementing an SMT can additionally implement this test suite specification in the code base's native language. Passing all tests in the concrete test suite is an indication of correctness and consistency with the reference specification; however, it is not an absolute guarantee.

The tests described in this document are designed to test features common to most Sparse Merkle Tree implementations. Test specifications are agnostic of the implementation details or language, and therefore take a black-box testing approach. A test specification may provide an example of what a compliant test may look like in the form of pseudocode.

A test specification follows the format:

  • Test name
  • Test description
  • Test inputs
  • Test outputs
  • Example pseudocode

For a concrete test to comply with its corresponding test specification, the System Under Test (SUT) must take in the prescribed inputs. When the SUT produces the prescribed outputs, the test passes. When the SUT produces any result or error that is not prescribed by the specification, the test fails. For a library to comply with the complete specification described herein, it must implement all test specifications, and each test must pass.

All test specifications assume that the Merkle Tree implementation under test uses the SHA-2-256 hashing algorithm as defined in FIPS PUB 180-4 to produce its outputs. The following test cases stipulate a theoretical function Sum(N) that takes in a big endian data slice N and returns the 32 byte SHA-256 hash of N.

Root Signature Tests

  1. Test Empty Root
  2. Test Update 1
  3. Test Update 2
  4. Test Update 3
  5. Test Update 5
  6. Test Update 10
  7. Test Update 100
  8. Test Update With Repeated Inputs
  9. Test Update Overwrite Key
  10. Test Update Union
  11. Test Update Sparse Union
  12. Test Update With Empty Data
  13. Test Update With Empty Data Performs Delete
  14. Test Update 1 Delete 1
  15. Test Update 2 Delete 1
  16. Test Update 10 Delete 5
  17. Test Delete Non-existent Key
  18. Test Interleaved Update Delete
  19. Test Delete Sparse Union

Test Empty Root

Description:

Tests the default root given no update or delete operations. The input set is described by S = {Ø}.

Inputs:

No inputs.

Outputs:

  • The expected root signature: 0x0000000000000000000000000000000000000000000000000000000000000000

Example pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 1

Description:

Tests the root after performing a single update call with the specified input.

Inputs:

  1. Update the empty tree with (K, D) where leaf key K = Sum(0u32) (32 bytes) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
root = smt.root()
expected_root = '39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 2

Description:

Tests the root after performing two update calls with the specified inputs.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree with (K, D), where leaf key K = Sum(1u32) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x8d0ae412ca9ca0afcb3217af8bcd5a673e798bd6fd1dfacad17711e883f494cb

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x01"), b"DATA")
root = smt.root()
expected_root = '8d0ae412ca9ca0afcb3217af8bcd5a673e798bd6fd1dfacad17711e883f494cb'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 3

Description:

Tests the root after performing three update calls with the specified inputs.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree with (K, D), where leaf key K = Sum(1u32) and leaf data D = b"DATA" (bytes, UTF-8)
  3. Update the tree with (K, D), where leaf key K = Sum(2u32) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x52295e42d8de2505fdc0cc825ff9fead419cbcf540d8b30c7c4b9c9b94c268b7

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x01"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x02"), b"DATA")
root = smt.root()
expected_root = '52295e42d8de2505fdc0cc825ff9fead419cbcf540d8b30c7c4b9c9b94c268b7'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 5

Description:

Tests the root after performing five update calls with the specified inputs.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree with (K, D), where leaf key K = Sum(1u32) and leaf data D = b"DATA" (bytes, UTF-8)
  3. Update the tree with (K, D), where leaf key K = Sum(2u32) and leaf data D = b"DATA" (bytes, UTF-8)
  4. Update the tree with (K, D), where leaf key K = Sum(3u32) and leaf data D = b"DATA" (bytes, UTF-8)
  5. Update the tree with (K, D), where leaf key K = Sum(4u32) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 10

Description:

Tests the root after performing 10 update calls with the specified inputs.

Inputs:

  1. For each i in 0..10, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x21ca4917e99da99a61de93deaf88c400d4c082991cb95779e444d43dd13e8849

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '21ca4917e99da99a61de93deaf88c400d4c082991cb95779e444d43dd13e8849'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 100

Description:

Tests the root after performing 100 update calls with the specified inputs.

Inputs:

  1. For each i in 0..100, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x82bf747d455a55e2f7044a03536fc43f1f55d43b855e72c0110c986707a23e4d

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..100 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '82bf747d455a55e2f7044a03536fc43f1f55d43b855e72c0110c986707a23e4d'
expect(hex_encode(root), expected_root).to_be_equal

Test Update With Repeated Inputs

Description:

Tests the root after performing two update calls with the same inputs. The resulting input set is described by S = {A} U {A} = {A}, where {A} is the input. This test expects a root signature identical to that produced by Test Update 1.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree again with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
root = smt.root()
expected_root = '39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b'
expect(hex_encode(root), expected_root).to_be_equal

Test Update Overwrite Key

Description:

Tests the root after performing two update calls with the same leaf keys but different leaf data. The second update call is expected to overwrite the data originally written by the first update call.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"CHANGE" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0xdd97174c80e5e5aa3a31c61b05e279c1495c8a07b2a08bca5dbc9fb9774f9457

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x00"), b"CHANGE")
root = smt.root()
expected_root = 'dd97174c80e5e5aa3a31c61b05e279c1495c8a07b2a08bca5dbc9fb9774f9457'
expect(hex_encode(root), expected_root).to_be_equal

Test Update Union

Description:

Tests the root after performing update calls with discontinuous sets of inputs. The resulting input set is described by S = [0..5) U [10..15) U [20..25).

Inputs:

  1. For each i in 0..5, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  2. For each i in 10..15, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  3. For each i in 20..25, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0x7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 10..15 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 20..25 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326'
expect(hex_encode(root), expected_root).to_be_equal

Test Update Sparse Union

Description:

Tests the root after performing update calls with discontinuous sets of inputs. The resulting input set is described by S = [0, 2, 4, 6, 8].

Inputs:

  1. For each i in 0..5, update the tree with (K, D), where leaf key K = Sum(i * 2) and leaf data D = b"DATA" (bytes, UTF-8)

Outputs:

  • The expected root signature: 0xe912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
    key = &(i as u32 * 2).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
root = smt.root()
expected_root = 'e912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696'
expect(hex_encode(root), expected_root).to_be_equal

Test Update With Empty Data

Description:

Tests the root after performing one update call with empty data. Updating the empty tree with empty data does not change the root, and the expected root remains the default root. The resulting input set is described by S = {Ø} U {Ø} = {Ø}. This test expects a root signature identical to that produced by Test Empty Root.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and empty leaf data D = b"" (0 bytes)

Outputs:

  • The expected root signature: 0x0000000000000000000000000000000000000000000000000000000000000000

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"")
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal

Test Update With Empty Data Performs Delete

Description:

Tests the root after performing one update call with arbitrary data followed by a second update call on the same key with empty data. Updating a key with empty data is equivalent to calling delete. By deleting the only key, we have an empty tree and expect to arrive at the default root. The resulting input set is described by S = {0} - {0} = {Ø}. This test expects a root signature identical to that produced by Test Empty Root.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree with (K, D), where leaf key K = Sum(0u32) and empty leaf data D = b"" (0 bytes)

Outputs:

  • The expected root signature: 0x0000000000000000000000000000000000000000000000000000000000000000

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x00"), b"")
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 1 Delete 1

Description:

Tests the root after performing one update call followed by a subsequent delete call on the same key. By deleting the only key, we have an empty tree and expect to arrive at the default root. The resulting input set is described by S = {0} - {0} = {Ø}. This test expects a root signature identical to that produced by Test Empty Root.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Delete (K) from the tree, where leaf key K = Sum(0u32)

Outputs:

  • The expected root signature: 0x0000000000000000000000000000000000000000000000000000000000000000

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.delete(&sum(b"\x00\x00\x00\x00"))
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 2 Delete 1

Description:

Tests the root after performing two update calls followed by a subsequent delete call on the first key. By deleting the second key, we have a tree with only one key remaining, equivalent to a single update. This test expects a root signature identical to that produced by Test Update 1.

Inputs:

  1. Update the empty tree with (K, D), where leaf key K = Sum(0u32) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Update the tree with (K, D), where leaf key K = Sum(1u32) and leaf data D = b"DATA" (bytes, UTF-8)
  3. Delete (K) from the tree, where leaf key K = Sum(1u32)

Outputs:

  • The expected root signature: 0x39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x01"), b"DATA")
smt.delete(&sum(b"\x00\x00\x00\x01"))
root = smt.root()
expected_root = '39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b'
expect(hex_encode(root), expected_root).to_be_equal

Test Update 10 Delete 5

Description:

Tests the root after performing 10 update calls followed by 5 subsequent delete calls on the latter keys. By deleting the last five keys, we have a tree with the first five keys remaining, equivalent to five updates. This test expects a root signature identical to that produced by Test Update 5.

Inputs:

  1. For each i in 0..10, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  2. For each i in 5..10, delete (K) from the tree, where leaf key K = Sum(i)

Outputs:

  • The expected root signature: 0x108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 5..10 {
    key = &(i as u32).to_big_endian_bytes()
    smt.delete(&sum(key))
}
root = smt.root()
expected_root = '108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b'
expect(hex_encode(root), expected_root).to_be_equal

Test Delete Non-existent Key

Description:

Tests the root after performing five update calls followed by a subsequent delete on a key that is not present in the input set. This test expects a root signature identical to that produced by Test Update 5.

Inputs:

  1. For each i in 0..5, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  2. Delete (K) from the tree, where leaf key K = Sum(1024u32)

Outputs:

  • The expected root signature: 0x108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
smt.delete(&sum(b"\x00\x00\x04\x00"))

root = smt.root()
expected_root = '108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b'
expect(hex_encode(root), expected_root).to_be_equal

Test Interleaved Update Delete

Description:

Tests the root after performing a series of interleaved update and delete calls. The resulting input set is described by [0..5) U [10..15) U [20..25). This test demonstrates the inverse relationship between operations update and delete. This test expects a root signature identical to that produced by Test Update Union.

Inputs:

  1. For each i in 0..10, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  2. For each i in 5..15, delete (K) from the tree, where leaf key K = Sum(i) from the tree
  3. For each i in 10..20, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  4. For each i in 15..25, delete (K) from the tree, where leaf key K = Sum(i) from the tree
  5. For each i in 20..30, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  6. For each i in 25..35, delete (K) from the tree, where leaf key K = Sum(i) from the tree

Outputs:

  • The expected root signature: 0x7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 5..15 {
    key = &(i as u32).to_big_endian_bytes()
    smt.delete(&sum(key))
}
for i in 10..20 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 15..25 {
    key = &(i as u32).to_big_endian_bytes()
    smt.delete(&sum(key))
}
for i in 20..30 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 25..35 {
    key = &(i as u32).to_big_endian_bytes()
    smt.delete(&sum(key))
}
root = smt.root()
expected_root = '7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326'
expect(hex_encode(root), expected_root).to_be_equal

Test Delete Sparse Union

Description:

Tests the root after performing delete calls with discontinuous sets of inputs. The resulting input set is described by S = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - [1, 3, 5, 7, 9] = [0, 2, 4, 6, 8]. This test expects a root signature identical to that produced by Test Update Sparse Union.

Inputs:

  1. For each i in 0..10, update the tree with (K, D), where leaf key K = Sum(i) and leaf data D = b"DATA" (bytes, UTF-8)
  2. For each i in 0..5, delete (K) from the tree, where leaf key K = Sum(i * 2 + 1)

Outputs:

  • The expected root signature: 0xe912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696

Example Pseudocode:

smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
    key = &(i as u32).to_big_endian_bytes()
    data = b"DATA"
    smt.update(&sum(key), data)
}
for i in 0..5 {
    key = &(i as u32 * 2 + 1).to_big_endian_bytes()
    smt.delete(&sum(key))
}
root = smt.root()
expected_root = 'e912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696'
expect(hex_encode(root), expected_root).to_be_equal