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
- Transaction Format - The Fuel transaction format.
- Computing Identifiers - Computing unique IDs for transactions, contracts and UTXOs.
- Transaction Validity - Defines transaction validity rules.
- Cryptographic Primitives - Cryptographic primitives used in Fuel.
- Application Binary Interface (ABI) - Low-level details on interfacing with Fuel bytecode.
- Storage Slot Initialization - JSON format for contract storage slot initialization.
- Block Header Format - The Fuel block header format.
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
- Proof of Authority (PoA) - The Fuel Proof of Authority Network.
Testing
- Sparse Merkle Tree - A test suite for verifying correctness of SMT outputs.
Transaction Format
The Fuel Transaction Format.
Constants
name | type | value | description |
---|---|---|---|
GAS_PER_BYTE | uint64 | Gas charged per byte of the transaction. | |
GAS_PRICE_FACTOR | uint64 | 1,000,000,000 | Unit factor for gas price. |
MAX_GAS_PER_TX | uint64 | Maximum gas per transaction. | |
MAX_INPUTS | uint64 | 8 | Maximum number of inputs. |
MAX_OUTPUTS | uint64 | 8 | Maximum number of outputs. |
MAX_PREDICATE_LENGTH | uint64 | Maximum length of predicate, in instructions. | |
MAX_GAS_PER_PREDICATE | uint64 | Maximum gas per predicate. | |
MAX_PREDICATE_DATA_LENGTH | uint64 | Maximum length of predicate data, in bytes. | |
MAX_SCRIPT_LENGTH | uint64 | Maximum length of script, in instructions. | |
MAX_SCRIPT_DATA_LENGTH | uint64 | Maximum length of script data, in bytes. | |
MAX_MESSAGE_DATA_LENGTH | uint16 | Maximum length of message data, in bytes. | |
MAX_STORAGE_SLOTS | uint16 | 255 | Maximum number of initial storage slots. |
MAX_WITNESSES | uint64 | 16 | Maximum number of witnesses. |
CHAIN_ID | uint64 | A unique per-chain identifier. |
Transaction
enum TransactionType : uint8 {
Script = 0,
Create = 1,
Mint = 2,
}
name | type | description |
---|---|---|
type | TransactionType | Transaction type. |
data | One of TransactionScript, TransactionCreate, or TransactionMint | Transaction data. |
Transaction is invalid if:
type > TransactionType.Create
gasLimit > MAX_GAS_PER_TX
blockheight() < maturity
inputsCount > MAX_INPUTS
outputsCount > MAX_OUTPUTS
witnessesCount > MAX_WITNESSES
- No inputs are of type
InputType.Coin
orInputType.Message
withinput.dataLength
== 0 - More than one output is of type
OutputType.Change
for any asset ID in the input set - 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
When serializing a transaction, fields are serialized as follows (with inner structs serialized recursively):
uint8
,uint16
,uint32
,uint64
: big-endian right-aligned to 8 bytes.byte[32]
: as-is.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
}
name | type | description |
---|---|---|
gasPrice | uint64 | Gas price for transaction. |
gasLimit | uint64 | Gas limit for transaction (including predicate gas). |
maturity | uint32 | Block until which tx cannot be included. |
scriptLength | uint16 | Script length, in instructions. |
scriptDataLength | uint16 | Length of script input data, in bytes. |
inputsCount | uint8 | Number of inputs. |
outputsCount | uint8 | Number of outputs. |
witnessesCount | uint8 | Number of witnesses. |
receiptsRoot | byte[32] | Merkle root of receipts. |
script | byte[] | Script to execute. |
scriptData | byte[] | Script input data (parameters). |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | 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)
gasLimit
is less than the sum of allpredicateGasUsed
forInputType.Coin
orInputType.Message
where predicate length is greater than zero.
Note: when signing a transaction,
receiptsRoot
is set to zero.Note: when verifying a predicate,
receiptsRoot
is initialized to zero.Note: when 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
name | type | description |
---|---|---|
gasPrice | uint64 | Gas price for transaction. |
gasLimit | uint64 | Gas limit for transaction (including predicate gas). |
maturity | uint32 | Block until which tx cannot be included. |
bytecodeLength | uint16 | Contract bytecode length, in instructions. |
bytecodeWitnessIndex | uint8 | Witness index of contract bytecode to create. |
storageSlotsCount | uint16 | Number of storage slots to initialize. |
inputsCount | uint8 | Number of inputs. |
outputsCount | uint8 | Number of outputs. |
witnessesCount | uint8 | Number of witnesses. |
salt | byte[32] | Salt. |
storageSlots | (byte[32], byte[32]])[] | List of storage slots to initialize (key, value). |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | List of witnesses. |
Transaction is invalid if:
- Any input is of type
InputType.Contract
orInputType.Message
whereinput.dataLength > 0
- Any output is of type
OutputType.Contract
orOutputType.Variable
orOutputType.Message
- More than one output is of type
OutputType.Change
withasset_id
of zero - Any output is of type
OutputType.Change
with non-zeroasset_id
- It does not have exactly one output of type
OutputType.ContractCreated
bytecodeLength * 4 > CONTRACT_MAX_SIZE
tx.data.witnesses[bytecodeWitnessIndex].dataLength != bytecodeLength * 4
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 oneOutputType.ContractCreated
output storageSlotsCount > MAX_STORAGE_SLOTS
- The Sparse Merkle tree root of
storageSlots
is not equal to thestateRoot
of the oneOutputType.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.
name | type | description |
---|---|---|
txPointer | TXPointer | The location of the Mint transaction in the block. |
inputContract | Input | The contract utxo that assets are minted to. |
outputContract | Output | The contract utxo that assets are being minted to. |
mintAmount | uint64 | The amount of funds minted. |
mintAssetId | byte[32] | The asset IDs corresponding to the minted amount. |
Transaction is invalid if:
txPointer
is zero or doesn't match the block.inputContract
is not of the typeInputType.Contract
outputContract
is not of the typeOutputType.Contract
Input
enum InputType : uint8 {
Coin = 0,
Contract = 1,
Message = 2,
}
name | type | description |
---|---|---|
type | InputType | Type of input. |
data | One of InputCoin, InputContract, or InputMessage | Input data. |
Transaction is invalid if:
type > InputType.Message
InputCoin
name | type | description |
---|---|---|
txID | byte[32] | Hash of transaction. |
outputIndex | uint8 | Index of transaction output. |
owner | byte[32] | Owning address or predicate root. |
amount | uint64 | Amount of coins. |
asset_id | byte[32] | Asset ID of the coins. |
txPointer | TXPointer | Points to the TX whose output is being spent. |
witnessIndex | uint8 | Index of witness that authorizes spending the coin. |
maturity | uint32 | UTXO being spent must have been created at least this many blocks ago. |
predicateGasUsed | uint64 | Gas used by predicate. |
predicateLength | uint16 | Length of predicate, in instructions. |
predicateDataLength | uint16 | Length of predicate input data, in bytes. |
predicate | byte[] | Predicate bytecode. |
predicateData | byte[] | 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 equalowner
predicateLength * 4 != len(predicate)
predicateDataLength != len(predicateData)
predicateGasUsed > MAX_GAS_PER_PREDICATE
If h
is the block height the UTXO being spent was created, transaction is invalid if blockheight() < h + maturity
.
Note: when signing a transaction,
txPointer
andpredicateGasUsed
is set to zero.Note: when verifying and estimating a predicate,
txPointer
andpredicateGasUsed
is initialized to zero.Note: when executing a script,
txPointer
is initialized to the TX whose output is being spent.
The predicate root is computed here.
InputContract
name | type | description |
---|---|---|
txID | byte[32] | Hash of transaction. |
outputIndex | uint8 | Index of transaction output. |
balanceRoot | byte[32] | Root of amount of coins owned by contract before transaction execution. |
stateRoot | byte[32] | State root of contract before transaction execution. |
txPointer | TXPointer | Points to the TX whose output is being spent. |
contractID | byte[32] | Contract ID. |
Transaction is invalid if:
- there is not exactly one output of type
OutputType.Contract
withinputIndex
equal to this input's index
Note: when signing a transaction,
txID
,outputIndex
,balanceRoot
,stateRoot
, andtxPointer
are set to zero.Note: when verifying a predicate,
txID
,outputIndex
,balanceRoot
,stateRoot
, andtxPointer
are initialized to zero.Note: when executing a script,
txID
,outputIndex
,balanceRoot
, andstateRoot
are initialized to the transaction ID, output index, amount, and state root of the contract with IDcontractID
, andtxPointer
is initialized to the TX whose output is being spent.
InputMessage
name | type | description |
---|---|---|
sender | byte[32] | The address of the message sender. |
recipient | byte[32] | The address or predicate root of the message recipient. |
amount | uint64 | Amount of base asset coins sent with message. |
nonce | byte[32] | The message nonce. |
witnessIndex | uint8 | Index of witness that authorizes spending the coin. |
predicateGasUsed | uint64 | Gas used by predicate execution. |
dataLength | uint16 | Length of message data, in bytes. |
predicateLength | uint16 | Length of predicate, in instructions. |
predicateDataLength | uint16 | Length of predicate input data, in bytes. |
data | byte[] | The message data. |
predicate | byte[] | Predicate bytecode. |
predicateData | byte[] | 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 equalrecipient
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 typeTransactionType.Script
with aScriptResult
receipt whereresult
is equal to0
indicating a successful script exitNote: 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,
}
name | type | description |
---|---|---|
type | OutputType | Type of output. |
data | One of OutputCoin, OutputContract, OutputChange, OutputVariable, or OutputContractCreated. | Output data. |
Transaction is invalid if:
type > OutputType.ContractCreated
OutputCoin
name | type | description |
---|---|---|
to | byte[32] | Receiving address or predicate root. |
amount | uint64 | Amount of coins to send. |
asset_id | byte[32] | Asset ID of coins. |
OutputContract
name | type | description |
---|---|---|
inputIndex | uint8 | Index of input contract. |
balanceRoot | byte[32] | Root of amount of coins owned by contract after transaction execution. |
stateRoot | byte[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
andstateRoot
are set to zero.Note: when verifying a predicate,
balanceRoot
andstateRoot
are initialized to zero.Note: when executing a script,
balanceRoot
andstateRoot
are initialized to the balance root and state root of the contract with IDtx.inputs[inputIndex].contractID
.
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
name | type | description |
---|---|---|
to | byte[32] | Receiving address or predicate root. |
amount | uint64 | Amount of coins to send. |
asset_id | byte[32] | Asset ID of coins. |
Transaction is invalid if:
- any other output has type
OutputType.OutputChange
and asset IDasset_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
name | type | description |
---|---|---|
to | byte[32] | Receiving address or predicate root. |
amount | uint64 | Amount of coins to send. |
asset_id | byte[32] | Asset ID of coins. |
Note: when signing a transaction,
to
,amount
, andasset_id
are set to zero.Note: when verifying a predicate or executing a script,
to
,amount
, andasset_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
name | type | description |
---|---|---|
contractID | byte[32] | Contract ID. |
stateRoot | byte[32] | Initial state root of contract. |
Witness
name | type | description |
---|---|---|
dataLength | uint16 | Length of witness data, in bytes. |
data | byte[] | 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.
name | type | description |
---|---|---|
blockHeight | uint32 | Block height. |
txIndex | uint16 | Transaction 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 ++ CHAIN_ID ++ 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 uint8
.
Message ID
The ID of a message is computed as the hash of:
- the sender address as
byte[32]
, - the recipient address as
byte[32]
, - the Message nonce as
byte[32]
, - the amount being sent with the message as
uint64
, - 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 uint8
: hash(byte[32] ++ uint8)
.
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 Lifecycle
- Access Lists
- VM Precondition Validity Rules
- Predicate Verification
- Script Execution
- VM Postcondition Validity Rules
Transaction Lifecycle
Once a transaction is seen, it goes through several stages of validation, in this order:
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:
- For each input
InputType.Coin
- The UTXO ID
(txID, outputIndex)
- The UTXO ID
- For each input
InputType.Contract
- The UTXO ID
(txID, outputIndex)
- The UTXO ID
- For each input
InputType.Message
- The message ID
messageID
- The message ID
Write-create access list:
- For each output
OutputType.ContractCreated
- The contract ID
contractID
- The contract ID
- For each output
- The created UTXO ID
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 themessages
message set until they are included in a transaction of typeTransactionType.Script
with aScriptResult
receipt whereresult
is equal to0
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 asset_id
in the input and output set:
def sum_data_messages(tx, asset_id) -> int:
"""
Returns the total balance available from messages containing data
"""
total: int = 0
if asset_id == 0:
for input in tx.inputs:
if input.type == InputType.Message and input.dataLength > 0:
total += input.amount
return total
def sum_inputs(tx, asset_id) -> int:
total: int = 0
for input in tx.inputs:
if input.type == InputType.Coin and input.asset_id == asset_id:
total += input.amount
elif input.type == InputType.Message and asset_id == 0 and input.dataLength == 0:
total += input.amount
return total
"""
Returns any minted amounts by the transaction
"""
def minted(tx, asset_id) -> int:
if tx.type != TransactionType.Mint or asset_id != tx.mint_asset_id:
return 0
return tx.mint_amount
def sum_outputs(tx, asset_id) -> int:
total: int = 0
for output in tx.outputs:
if output.type == OutputType.Coin and output.asset_id == asset_id:
total += output.amount
return total
def available_balance(tx, asset_id) -> int:
"""
Make the data message balance available to the script
"""
availableBalance = sum_inputs(tx, asset_id) + sum_data_messages(tx, asset_id) + minted(tx, asset_id)
return availableBalance
def unavailable_balance(tx, asset_id) -> int:
sentBalance = sum_outputs(tx, asset_id)
gasBalance = gasPrice * gasLimit / GAS_PRICE_FACTOR
# Size excludes witness data as it is malleable (even by third parties!)
bytesBalance = size(tx) * GAS_PER_BYTE * gasPrice / GAS_PRICE_FACTOR
# Total fee balance
feeBalance = ceiling(gasBalance + bytesBalance)
# Only base asset can be used to pay for gas
if asset_id == 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, asset_id) >= (unavailable_balance(tx, asset_id) + sum_data_messages(tx, asset_id))
Valid Signatures
def address_from(pubkey: bytes) -> bytes:
return sha256(pubkey)[0:32]
for input in tx.inputs:
if (input.type == InputType.Coin || 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 asset_id
in the input set, the free balance available to be moved around by the script and called contracts is freeBalance[asset_id]
. The initial message balance available to be moved around by the script and called contracts is messageBalance
:
freeBalance[asset_id] = available_balance(tx, asset_id) - unavailable_balance(tx, asset_id)
messageBalance = sum_data_messages(tx, 0)
Once the free balances are computed, the script is executed. After execution, the following is extracted:
- The transaction in-memory on VM termination is used as the final transaction which is included in the block.
- The unspent free balance
unspentBalance
for each asset ID. - The unspent gas
unspentGas
from the$ggas
register.
The fees incurred for a transaction are ceiling(((size(tx) * GAS_PER_BYTE) + (tx.gasLimit - unspentGas)) * tx.gasPrice / GAS_PRICE_FACTOR)
.
If the transaction as included in a block does not match this final transaction, the block is invalid.
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
; anamount
ofunspentBalance + floor((unspentGas * tx.gasPrice) / GAS_PRICE_FACTOR)
- otherwise; an
amount
of the unspent free balance for that asset ID after VM execution is complete
- if the asset ID is
- if the transaction reverts;
- if the asset ID is
0
; anamount
of the initial free balance plus(unspentGas * tx.gasPrice) - messageBalance
- otherwise; an
amount
of the initial free balance for that asset ID.
- if the asset ID is
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:
- It must be a Mint transaction.
- 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.
- The
mintAmount
doesn't exceed the total amount of fees processed from all other transactions within the same block. - The
mintAssetId
matches theasset_id
that fees are paid in (asset_id == 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:
name | type | description |
---|---|---|
v | HashDigest | Node 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
name | type | description |
---|---|---|
root | HashDigest[] | The expected root of the Merkle tree. |
data | Bytes | The data of the leaf (unhashed). |
siblings | HashDigest[] | 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:
- Default values are given to leaf nodes with empty leaves.
- 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. - 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:
name | type | description |
---|---|---|
v | HashDigest | Node 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:
name | type | description |
---|---|---|
depth | uint16 | Depth of the leaf node. The root node is at depth 0 . Must be <= 256 . |
siblings | HashDigest[] | Sibling hash values, ordered starting from the leaf's neighbor. |
includedSiblings | byte[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 (https://github.com/bitcoin-core/secp256k1), with wrappers in Go (https://pkg.go.dev/github.com/ethereum/go-ethereum/crypto/secp256k1) and Rust (https://docs.rs/crate/secp256k1).
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.
name | type | description |
---|---|---|
da_height | uint64 | Height of the data availability layer up to which (inclusive) input messages are processed. |
txCount | uint64 | Number of transactions in this block. |
message_receipt_count | uint64 | Number of output messages in this block. |
txRoot | byte[32] | Merkle root of transactions in this block. |
message_receipt_root | byte[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 { .. }
andstruct Bar<T> { .. }
in the example above are both type declarations. - type application: the application or use of a type.
Foo
andBar<u64>
infn baz(input1: Foo, input2: Bar<u64>);
in the example above are both applications of the type declarationsstruct Foo { .. }
andstruct Bar<T> { .. }
respectively. - type parameter: a generic parameter used in a type declaration.
T
instruct Bar<T>
in the example above is a type parameter. - type argument: an application of a type parameter used in a type application.
u64
ininput2: 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, andnull
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, andnull
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, andnull
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, andnull
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, andnull
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, andnull
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, andnull
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, andnull
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 oflog
orlogd
in the contract's bytecode. Each instance is a JSON object that contains the following properties:"logId"
: a unique integer ID. Thelog
andlogd
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, andnull
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, andnull
otherwise. The format of the elements of this array recursively follows the rules described in this section.
"messagesTypes"
: an array describing all instances ofsmo
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, andnull
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, andnull
otherwise. The format of the elements of this array recursively follows the rules described in this section.
"configurables"
: an array describing allconfigurable
variables used in the contract. Eachconfigurable
variable is represented as a JSON object that contains the following properties:"name"
: the name of theconfigurable
variable."configurableType"
: a type application represented as a JSON object that contains the following properties:"type"
: the type declaration ID of the type of theconfigurable
variable."typeArguments"
: an array of the type arguments used when applying the type of theconfigurable
variable, if the type is generic, andnull
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, andnull
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 theconfigurable
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 name | Attribute arguments | Semantics |
---|---|---|
storage | read and/or write | Specifies if a function reads or writes to/from storage |
payable | None | Specifies if a function can accept coins: a function without payable attribute must not accept coins |
test | None | Specifies if a function is a unit test |
inline | never or always , but not both | Specifies if a function should be inlined during code generation |
doc-comment | String | Documentation comment |
doc | Not defined yet | Not 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:
type
: String; the type of the receipt. Can be one of:
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 ofMEM[$rC, $rD]
.data
: Hexadecimal string representation of the value of the memory rangeMEM[$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 ofMEM[$rA, $rB]
.data
: Hexadecimal string representation of the value of the memory rangeMEM[$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 ofMEM[$rB, $rC]
.data
: Hexadecimal string representation of the value of the memory rangeMEM[$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
Type | Encoding |
---|---|
bool | bool |
u8 | u8 |
u16 | u16 |
u32 | u32 |
u64 | u64 |
b256 | b256 |
struct | s<<arg1>,<arg2>,...>(<ty1>,<ty2>,...) where <ty1> , <ty2> , ... are the encoded types of the struct fields and <arg1> , <arg2> , ... are the encoded type arguments |
enum | e<<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>] |
array | a[<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
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.
- Boolean:
bool
, either0
or1
encoded identically tou8
. - B256:
b256
, arbitrary 256-bits value. - Address :
address
, a 256-bit (32-byte) address. - Fixed size string
- Array
- Enums (sum types)
- Structs
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, or 64 bits.
enc(X)
is the big-endian (i.e. right-aligned) representation of X
left-padded with zero-bytes. Total length must be 8 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.
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:
0x0000000000000001
, thetrue
bool encoded in-place.0x0000000000000001
, First element of the parameter[u64; 2]
,1
, encoded as au64
.0x0000000000000002
, Second element of the parameter[u64; 2]
,2
, encoded as au64
.
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`.
Fuel VM Specification
- Introduction
- Parameters
- Semantics
- Flags
- Instruction Set
- VM Initialization
- Contexts
- Predicate Estmation
- Predicate Verification
- Script Execution
- Call Frames
- Ownership
Introduction
This document provides the specification for the Fuel Virtual Machine (FuelVM). The specification covers the types, instruction set, and execution semantics.
Parameters
name | type | value | note |
---|---|---|---|
CONTRACT_MAX_SIZE | uint64 | Maximum contract size, in bytes. | |
MEM_MAX_ACCESS_SIZE | uint64 | Maximum memory access size, in bytes. | |
VM_MAX_RAM | uint64 | 2**26 | 64 MiB. |
MESSAGE_MAX_DATA_SIZE | uint16 | Maximum 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:
value | register | name | description |
---|---|---|---|
0x00 | $zero | zero | Contains zero (0 ), for convenience. |
0x01 | $one | one | Contains one (1 ), for convenience. |
0x02 | $of | overflow | Contains overflow/underflow of addition, subtraction, and multiplication. |
0x03 | $pc | program counter | The program counter. Memory address of the current instruction. |
0x04 | $ssp | stack start pointer | Memory address of bottom of current writable stack area. |
0x05 | $sp | stack pointer | Memory address on top of current writable stack area (points to free memory). |
0x06 | $fp | frame pointer | Memory address of beginning of current call frame. |
0x07 | $hp | heap pointer | Memory address below the current bottom of the heap (points to used/oob memory). |
0x08 | $err | error | Error codes for particular operations. |
0x09 | $ggas | global gas | Remaining gas globally. |
0x0A | $cgas | context gas | Remaining gas in the context. |
0x0B | $bal | balance | Received balance for this context. |
0x0C | $is | instrs start | Pointer to the start of the currently-executing code. |
0x0D | $ret | return value | Return value or pointer. |
0x0E | $retl | return length | Return value length in bytes. |
0x0F | $flag | flags | Flags 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
value | name | description |
---|---|---|
0x01 | F_UNSAFEMATH | If set, undefined arithmetic zeroes target and sets $err without panic. |
0x02 | F_WRAPPING | If 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:
- Transaction hash (
byte[32]
, word-aligned), computed as defined here. MAX_INPUTS
pairs of(asset_id: byte[32], balance: uint64)
, of:- For predicate estimation and predicate verification, zeroes.
- 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.
- Transaction length, in bytes (
uint64
, word-aligned). - The transaction, serialized.
Then the following registers are initialized (without explicit initialization, all registers are initialized to zero):
$ssp = 32 + MAX_INPUTS*(32+8) + size(tx))
: the writable stack area starts immediately after the serialized transaction in memory (see above).$sp = $ssp
: writable stack area is empty to start.$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:
$pc
and$is
are set to the start of the input'spredicate
field.$ggas
and$cgas
are set to the minimum oftx.gasLimit
orMAX_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
:
- 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 tx.gasLimit - $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:
$pc
and$is
are set to the start of the input'spredicate
field.$ggas
and$cgas
are set topredicateGasUsed
.
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:
$pc
and$is
are set to the start of the transaction's script bytecode.$ggas
and$cgas
are set totx.gasLimit
minus the sum ofpredicateGasUsed
for all predicates.
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
.
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:
- 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.
- 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:
bytes | type | value | description |
---|---|---|---|
Unwritable area begins. | |||
32 | byte[32] | to | Contract ID for this call. |
32 | byte[32] | asset_id | asset ID of forwarded coins. |
8*64 | byte[8][64] | regs | Saved registers from previous context. |
8 | uint16 | codesize | Code size in bytes, padded to word alignment. |
8 | byte[8] | param1 | First parameter. |
8 | byte[8] | param2 | Second parameter. |
1* | byte[] | code | Zero-padded to 8-byte alignment, but individual instructions are not aligned. |
Unwritable area ends. | |||
* | Call frame's stack. |
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:
[$ssp, $sp)
: the writable stack area.[$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:
[$ssp, $sp)
: the writable stack area of the call frame.[$hp, $fp->$hp)
: the heap area allocated by this call frame.
FuelVM Instruction Set
- Reading Guide
- Arithmetic/Logic (ALU) Instructions
- ADD: Add
- ADDI: Add immediate
- AND: AND
- ANDI: AND immediate
- DIV: Divide
- DIVI: Divide immediate
- EQ: Equals
- EXP: Exponentiate
- EXPI: Exponentiate immediate
- GT: Greater than
- LT: Less than
- MLOG: Math logarithm
- MOD: Modulus
- MODI: Modulus immediate
- MOVE: Move
- MOVI: Move immediate
- MROO: Math root
- MUL: Multiply
- MULI: Multiply immediate
- MLDV: Fused multiply-divide
- NOOP: No operation
- NOT: Invert
- OR: OR
- ORI: OR immediate
- SLL: Shift left logical
- SLLI: Shift left logical immediate
- SRL: Shift right logical
- SRLI: Shift right logical immediate
- SUB: Subtract
- SUBI: Subtract immediate
- SUBI: Subtract immediate
- WDCM: 128-bit integer comparison
- WQCM: 256-bit integer comparison
- WDOP: Misc 128-bit integer operations
- WQOP: Misc 256-bit integer operations
- WDML: Multiply 128-bit integers
- WQML: Multiply 256-bit integers
- WDDV: 128-bit integer division
- WQDV: 256-bit integer division
- WDMD: 128-bit integer fused multiply-divide
- WQMD: 256-bit integer fused multiply-divide
- WDAM: Modular 128-bit integer addition
- WQAM: Modular 256-bit integer addition
- WDMM: Modular 128-bit integer multiplication
- WQMM: Modular 256-bit integer multiplication
- XOR: XOR
- XORI: XOR immediate
- Control Flow Instructions
- JMP: Jump
- JI: Jump immediate
- JNE: Jump if not equal
- JNEI: Jump if not equal immediate
- JNZI: Jump if not zero immediate
- JMPB: Jump relative backwards
- JMPF: Jump relative forwards
- JNZB: Jump if not zero relative backwards
- JNZF: Jump if not zero relative forwards
- JNEB: Jump if not equal relative backwards
- JNEF: Jump if not equal relative forwards
- RET: Return from context
- Memory Instructions
- ALOC: Allocate memory
- CFE: Extend call frame
- CFEI: Extend call frame immediate
- CFS: Shrink call frame
- CFSI: Shrink call frame immediate
- LB: Load byte
- LW: Load word
- MCL: Memory clear
- MCLI: Memory clear immediate
- MCP: Memory copy
- MCPI: Memory copy immediate
- MEQ: Memory equality
- POPH: Pop a set of high registers from stack
- POPL: Pop a set of low registers from stack
- PSHH: Push a set of high registers to stack
- PSHL: Push a set of low registers to stack
- SB: Store byte
- SW: Store word
- Contract Instructions
- BAL: Balance of contract ID
- BHEI: Block height
- BHSH: Block hash
- BURN: Burn existing coins
- CALL: Call contract
- CB: Coinbase address
- CCP: Code copy
- CROO: Code Merkle root
- CSIZ: Code size
- LDC: Load code from an external contract
- LOG: Log event
- LOGD: Log data event
- MINT: Mint new coins
- RETD: Return from context with data
- RVRT: Revert
- SMO: Send message to output
- SCWQ: State clear sequential 32 byte slots
- SRW: State read word
- SRWQ: State read sequential 32 byte slots
- SWW: State write word
- SWWQ: State write sequential 32 byte slots
- TIME: Timestamp at height
- TR: Transfer coins to contract
- TRO: Transfer coins to output
- Cryptographic Instructions
- Other Instructions
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 bytex
, of lengthy
bytes. - The syntax
STATE[x, y]
used in this page means the sequence of storage slots starting at keyx
and spanningy
bytes.
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, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Panic |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
then append an additional receipt to the list of receipts, again modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 1 |
gas_used | uint64 | Gas consumed by the script. |
A few instructions are annotated with the effects they produce, the table below explains each effect:
effect name | description |
---|---|
Storage read | Instruction reads from storage slots |
Storage write | Instruction writes to storage slots |
External call | External contract call instruction |
Balance tree read | Instruction reads from the balance tree |
Balance tree write | Instruction writes to the balance tree |
Output message | Instruction 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
Description | Adds two registers. |
Operation | $rA = $rB + $rC; |
Syntax | add $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
is assigned the overflow of the operation.
$err
is cleared.
ADDI: Add immediate
Description | Adds a register and an immediate value. |
Operation | $rA = $rB + imm; |
Syntax | addi $rA, $rB, immediate |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
$of
is assigned the overflow of the operation.
$err
is cleared.
AND: AND
Description | Bitwise ANDs two registers. |
Operation | $rA = $rB & $rC; |
Syntax | and $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
ANDI: AND immediate
Description | Bitwise ANDs a register and an immediate value. |
Operation | $rA = $rB & imm; |
Syntax | andi $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
imm
is extended to 64 bits, with the high 52 bits set to 0
.
$of
and $err
are cleared.
DIV: Divide
Description | Divides two registers. |
Operation | $rA = $rB // $rC; |
Syntax | div $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
If $rC == 0
, $rA
is cleared and $err
is set to true
.
Otherwise, $err
is cleared.
$of
is cleared.
DIVI: Divide immediate
Description | Divides a register and an immediate value. |
Operation | $rA = $rB // imm; |
Syntax | divi $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
If imm == 0
, $rA
is cleared and $err
is set to true
.
Otherwise, $err
is cleared.
$of
is cleared.
EQ: Equals
Description | Compares two registers for equality. |
Operation | $rA = $rB == $rC; |
Syntax | eq $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
EXP: Exponentiate
Description | Raises one register to the power of another. |
Operation | $rA = $rB ** $rC; |
Syntax | exp $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
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
Description | Raises one register to the power of an immediate value. |
Operation | $rA = $rB ** imm; |
Syntax | expi $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
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
Description | Compares two registers for greater-than. |
Operation | $rA = $rB > $rC; |
Syntax | gt $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
LT: Less than
Description | Compares two registers for less-than. |
Operation | $rA = $rB < $rC; |
Syntax | lt $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
MLOG: Math logarithm
Description | The (integer) logarithm base $rC of $rB . |
Operation | $rA = math.floor(math.log($rB, $rC)); |
Syntax | mlog $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
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
Description | Modulo remainder of two registers. |
Operation | $rA = $rB % $rC; |
Syntax | mod $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
If $rC == 0
, both $rA
and $of
are cleared and $err
is set to true
.
Otherwise, $of
and $err
are cleared.
MODI: Modulus immediate
Description | Modulo remainder of a register and an immediate value. |
Operation | $rA = $rB % imm; |
Syntax | modi $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
If imm == 0
, both $rA
and $of
are cleared and $err
is set to true
.
Otherwise, $of
and $err
are cleared.
MOVE: Move
Description | Copy from one register to another. |
Operation | $rA = $rB; |
Syntax | move $rA, $rB |
Encoding | 0x00 rA rB - - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
MOVI: Move immediate
Description | Copy an immediate value into a register. |
Operation | $rA = imm; |
Syntax | movi $rA, imm |
Encoding | 0x00 rA i i i |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
MROO: Math root
Description | The (integer) $rC th root of $rB . |
Operation | $rA = math.floor(math.root($rB, $rC)); |
Syntax | mroo $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
If $rC == 0
, both $rA
and $of
are cleared and $err
is set to true
.
Otherwise, $of
and $err
are cleared.
MUL: Multiply
Description | Multiplies two registers. |
Operation | $rA = $rB * $rC; |
Syntax | mul $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
is assigned the overflow of the operation.
$err
is cleared.
MULI: Multiply immediate
Description | Multiplies a register and an immediate value. |
Operation | $rA = $rB * imm; |
Syntax | mul $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
$of
is assigned the overflow of the operation.
$err
is cleared.
MLDV: Fused multiply-divide
Description | Multiplies two registers with arbitrary precision, then divides by a third register. |
Operation | a = (b * c) / d; |
Syntax | mldv $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Notes | Division 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
Description | Performs no operation. |
Operation | |
Syntax | noop |
Encoding | 0x00 - - - - |
Notes |
$of
and $err
are cleared.
NOT: Invert
Description | Bitwise NOT a register. |
Operation | $rA = ~$rB; |
Syntax | not $rA, $rB |
Encoding | 0x00 rA rB - - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
OR: OR
Description | Bitwise ORs two registers. |
Operation | $rA = $rB \| $rC; |
Syntax | or $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
ORI: OR immediate
Description | Bitwise ORs a register and an immediate value. |
Operation | $rA = $rB \| imm; |
Syntax | ori $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
imm
is extended to 64 bits, with the high 52 bits set to 0
.
$of
and $err
are cleared.
SLL: Shift left logical
Description | Left shifts a register by a register. |
Operation | $rA = $rB << $rC; |
Syntax | sll $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes | Zeroes are shifted in. |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
SLLI: Shift left logical immediate
Description | Left shifts a register by an immediate value. |
Operation | $rA = $rB << imm; |
Syntax | slli $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes | Zeroes are shifted in. |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
SRL: Shift right logical
Description | Right shifts a register by a register. |
Operation | $rA = $rB >> $rC; |
Syntax | srl $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes | Zeroes are shifted in. |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
SRLI: Shift right logical immediate
Description | Right shifts a register by an immediate value. |
Operation | $rA = $rB >> imm; |
Syntax | srli $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes | Zeroes are shifted in. |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
SUB: Subtract
Description | Subtracts two registers. |
Operation | $rA = $rB - $rC; |
Syntax | sub $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes | $of is assigned the overflow of the operation. |
Panic if:
$rA
is a reserved register
$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
Description | Subtracts a register and an immediate value. |
Operation | $rA = $rB - imm; |
Syntax | subi $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes | $of is assigned the overflow of the operation. |
Panic if:
$rA
is a reserved register
$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
Description | Compare or examine two 128-bit integers using selected mode |
Operation | b = mem[$rB,16]; c = indirect?mem[$rC,16]:$rC; $rA = cmp_op(b,c); |
Syntax | wdcm $rA, $rB, $rC, imm |
Encoding | 0x00 rA rB rC i |
Notes |
The six-bit immediate value is used to select operating mode, as follows:
Bits | Short name | Description |
---|---|---|
...XXX | mode | Compare mode selection |
.XX... | reserved | Reserved and must be zero |
X..... | indirect | Is rhs operand ($rC) indirect or not |
Then the actual operation that's performed:
mode | Name | Description |
---|---|---|
0 | eq | Equality (== ) |
1 | ne | Inequality (!= ) |
2 | lt | Less than (< ) |
3 | gt | Greater than (> ) |
4 | lte | Less than or equals (<= ) |
5 | gte | Greater than or equals (>= ) |
6 | lzc | Leading 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
Description | Compare or examine two 256-bit integers using selected mode |
Operation | b = mem[$rB,32]; c = indirect?mem[$rC,32]:$rC; $rA = cmp_op(b,c); |
Syntax | wqcm $rA, $rB, $rC, imm |
Encoding | 0x00 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
Description | Perform an ALU operation on two 128-bit integers |
Operation | b = mem[$rB,16]; c = indirect?mem[$rC,16]:$rC; mem[$rA,16] = op(b,c); |
Syntax | wdop $rA, $rB, $rC, imm |
Encoding | 0x00 rA rB rC i |
Notes |
The six-bit immediate value is used to select operating mode, as follows:
Bits | Short name | Description |
---|---|---|
...XXX | op | Operation selection, see below |
.XX... | reserved | Reserved and must be zero |
X..... | indirect | Is rhs operand ($rC) indirect or not |
Then the actual operation that's performed:
op | Name | Description |
---|---|---|
0 | add | Add |
1 | sub | Subtract |
2 | not | Invert bits (discards rhs) |
3 | or | Bitwise or |
4 | xor | Bitwise exclusive or |
5 | and | Bitwise and |
6 | shl | Shift left (logical) |
7 | shr | Shift 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
Description | Perform an ALU operation on two 256-bit integers |
Operation | b = mem[$rB,32]; c = indirect?mem[$rC,32]:$rC; mem[$rA,32] = op(b,c); |
Syntax | wqop $rA, $rB, $rC, imm |
Encoding | 0x00 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
Description | Perform integer multiplication operation on two 128-bit integers. |
Operation | b=indirect0?mem[$rB,16]:$rB; c=indirect1?mem[$rC,16]:$rC; mem[$rA,16]=b*c; |
Syntax | wdml $rA, $rB, $rC, imm |
Encoding | 0x00 rA rB rC i |
Notes |
The six-bit immediate value is used to select operating mode, as follows:
Bits | Short name | Description |
---|---|---|
..XXXX | reserved | Reserved and must be zero |
.X.... | indirect0 | Is lhs operand ($rB) indirect or not |
X..... | indirect1 | Is 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
Description | Perform integer multiplication operation on two 256-bit integers. |
Operation | b=indirect0?mem[$rB,32]:$rB; c=indirect1?mem[$rC,32]:$rC; mem[$rA,32]=b*c; |
Syntax | wqml $rA, $rB, $rC, imm |
Encoding | 0x00 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
Description | Divide a 128-bit integer by another. |
Operation | b = mem[$rB,16]; c = indirect?mem[$rC,16]:$rC; mem[$rA,16] = b / c; |
Syntax | wddv $rA, $rB, $rC, imm |
Encoding | 0x00 rA rB rC i |
Notes |
The six-bit immediate value is used to select operating mode, as follows:
Bits | Short name | Description |
---|---|---|
.XXXXX | reserved | Reserved and must be zero |
X..... | indirect | Is 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
Description | Divide a 256-bit integer by another. |
Operation | b = mem[$rB,32]; c = indirect?mem[$rC,32]:$rC; mem[$rA,32] = b / c; |
Syntax | wqdv $rA, $rB, $rC, imm |
Encoding | 0x00 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
Description | Combined multiply-divide of 128-bit integers with arbitrary precision. |
Operation | b=mem[$rB,16]; c=mem[$rC,16]; d=mem[$rD,16]; mem[$rA,16]=(b * c) / d; |
Syntax | wddv $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Notes | Division 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
Description | Combined multiply-divide of 256-bit integers with arbitrary precision. |
Operation | b=mem[$rB,32]; c=mem[$rC,32]; d=mem[$rD,32]; mem[$rA,32]=(b * c) / d; |
Syntax | wqdv $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Notes | Division 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
Description | Add two 128-bit integers and compute modulo remainder with arbitrary precision. |
Operation | b=mem[$rB,16]; c=mem[$rC,16]; d=mem[$rD,16]; mem[$rA,16] = (b+c)%d; |
Syntax | wdam $rA, $rB, $rC, $rD |
Encoding | 0x00 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
Description | Add two 256-bit integers and compute modulo remainder with arbitrary precision. |
Operation | b=mem[$rB,32]; c=mem[$rC,32]; d=mem[$rD,32]; mem[$rA,32] = (b+c)%d; |
Syntax | wdam $rA, $rB, $rC, $rD |
Encoding | 0x00 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
Description | Multiply two 128-bit integers and compute modulo remainder with arbitrary precision. |
Operation | b=mem[$rB,16]; c=mem[$rC,16]; d=mem[$rD,16]; mem[$rA,16] = (b*c)%d; |
Syntax | wdmm $rA, $rB, $rC, $rD |
Encoding | 0x00 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
Description | Multiply two 256-bit integers and compute modulo remainder with arbitrary precision. |
Operation | b=mem[$rB,32]; c=mem[$rC,32]; d=mem[$rD,32]; mem[$rA,32] = (b*c)%d; |
Syntax | wqmm $rA, $rB, $rC, $rD |
Encoding | 0x00 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
Description | Bitwise XORs two registers. |
Operation | $rA = $rB ^ $rC; |
Syntax | xor $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
XORI: XOR immediate
Description | Bitwise XORs a register and an immediate value. |
Operation | $rA = $rB ^ imm; |
Syntax | xori $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register
$of
and $err
are cleared.
Control Flow Instructions
JMP: Jump
Description | Jumps to the code instruction offset by a register. |
Operation | $pc = $is + $rA * 4; |
Syntax | jmp $rA |
Encoding | 0x00 rA - - - |
Notes |
Panic if:
$is + $rA * 4 > VM_MAX_RAM - 1
JI: Jump immediate
Description | Jumps to the code instruction offset by imm . |
Operation | $pc = $is + imm * 4; |
Syntax | ji imm |
Encoding | 0x00 i i i i |
Notes |
Panic if:
$is + imm * 4 > VM_MAX_RAM - 1
JNE: Jump if not equal
Description | Jump to the code instruction offset by a register if $rA is not equal to $rB . |
Operation | if $rA != $rB: $pc = $is + $rC * 4; else: $pc += 4; |
Syntax | jne $rA $rB $rC |
Encoding | 0x00 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
Description | Jump to the code instruction offset by imm if $rA is not equal to $rB . |
Operation | if $rA != $rB: $pc = $is + imm * 4; else: $pc += 4; |
Syntax | jnei $rA $rB imm |
Encoding | 0x00 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
Description | Jump to the code instruction offset by imm if $rA is not equal to $zero . |
Operation | if $rA != $zero: $pc = $is + imm * 4; else: $pc += 4; |
Syntax | jnzi $rA imm |
Encoding | 0x00 rA i i i |
Notes |
Panic if:
$is + imm * 4 > VM_MAX_RAM - 1
and the jump would be performed (i.e.$rA != $zero
)
JMPB: Jump relative backwards
Description | Jump $rA + imm instructions backwards. |
Operation | $pc -= ($rA + imm + 1) * 4; |
Syntax | jmpb $rA imm |
Encoding | 0x00 rA i i i |
Notes |
Panic if:
$pc - ($rA + imm + 1) * 4 < 0
JMPF: Jump relative forwards
Description | Jump $rA + imm instructions forwards |
Operation | $pc += ($rA + imm + 1) * 4; |
Syntax | jmpf $rA imm |
Encoding | 0x00 rA i i i |
Notes |
Panic if:
$pc + ($rA + imm + 1) * 4 > VM_MAX_RAM - 1
JNZB: Jump if not zero relative backwards
Description | Jump $rB + imm instructions backwards if $rA != $zero . |
Operation | if $rA != $zero: $pc -= ($rB + imm + 1) * 4; else: $pc += 4; |
Syntax | jnzb $rA $rB imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$pc - ($rB + imm + 1) * 4 < 0
JNZF: Jump if not zero relative forwards
Description | Jump $rB + imm instructions forwards if $rA != $zero . |
Operation | if $rA != $zero: $pc += ($rB + imm + 1) * 4; else: $pc += 4; |
Syntax | jnzf $rA $rB imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$pc + ($rB + imm + 1) * 4 > VM_MAX_RAM - 1
JNEB: Jump if not equal relative backwards
Description | Jump $rC + imm instructions backwards if $rA != $rB . |
Operation | if $rA != $rB: $pc -= ($rC + imm + 1) * 4; else: $pc += 4; |
Syntax | jneb $rA $rB $rC imm |
Encoding | 0x00 rA rB rC i |
Notes |
Panic if:
$pc - ($rC + imm + 1) * 4 < 0
JNEF: Jump if not equal relative forwards
Description | Jump $rC + imm instructions forwards if $rA != $rB . |
Operation | if $rA != $rB: $pc += ($rC + imm + 1) * 4; else: $pc += 4; |
Syntax | jnef $rA $rB $rC imm |
Encoding | 0x00 rA rB rC i |
Notes |
Panic if:
$pc + ($rC + imm + 1) * 4 > VM_MAX_RAM - 1
RET: Return from context
Description | Returns from context with value $rA . |
Operation | return($rA); |
Syntax | ret $rA |
Encoding | 0x00 rA - - - |
Notes |
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Return |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val | uint64 | Value of register $rA . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
If current context is external, append an additional receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 0 |
gas_used | uint64 | Gas 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:
$cgas = $cgas + $fp->$cgas
(add remaining context gas from previous context to current remaining context gas)
Set the return value:
$ret = $rA
$retl = 0
Then pop the call frame and restore all registers except $ggas
, $cgas
, $ret
, $retl
and $hp
. Afterwards, set the following registers:
$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
Description | Allocate a number of bytes from the heap. |
Operation | $hp = $hp - $rA; |
Syntax | aloc $rA |
Encoding | 0x00 rA - - - |
Notes | Does not initialize memory. |
Panic if:
$hp - $rA
underflows$hp - $rA < $sp
CFE: Extend call frame
Description | Extend the current call frame's stack. |
Operation | $sp = $sp + $rA |
Syntax | cfei $rA |
Encoding | 0x00 rA - - - |
Notes | Does not initialize memory. |
Panic if:
$sp + $rA
overflows$sp + $rA > $hp
CFEI: Extend call frame immediate
Description | Extend the current call frame's stack by an immediate value. |
Operation | $sp = $sp + imm |
Syntax | cfei imm |
Encoding | 0x00 i i i i |
Notes | Does not initialize memory. |
Panic if:
$sp + imm
overflows$sp + imm > $hp
CFS: Shrink call frame
Description | Shrink the current call frame's stack. |
Operation | $sp = $sp - $rA |
Syntax | cfs $rA |
Encoding | 0x00 $rA - - - |
Notes | Does not clear memory. |
Panic if:
$sp - $rA
underflows$sp - $rA < $ssp
CFSI: Shrink call frame immediate
Description | Shrink the current call frame's stack by an immediate value. |
Operation | $sp = $sp - imm |
Syntax | cfsi imm |
Encoding | 0x00 i i i i |
Notes | Does not clear memory. |
Panic if:
$sp - imm
underflows$sp - imm < $ssp
LB: Load byte
Description | A byte is loaded from the specified address offset by imm . |
Operation | $rA = MEM[$rB + imm, 1]; |
Syntax | lb $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Panic if:
$rA
is a reserved register$rB + imm + 1
overflows$rB + imm + 1 > VM_MAX_RAM
LW: Load word
Description | A word is loaded from the specified address offset by imm . |
Operation | $rA = MEM[$rB + (imm * 8), 8]; |
Syntax | lw $rA, $rB, imm |
Encoding | 0x00 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
Description | Clear bytes in memory. |
Operation | MEM[$rA, $rB] = 0; |
Syntax | mcl $rA, $rB |
Encoding | 0x00 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
Description | Clear bytes in memory. |
Operation | MEM[$rA, imm] = 0; |
Syntax | mcli $rA, imm |
Encoding | 0x00 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
Description | Copy bytes in memory. |
Operation | MEM[$rA, $rC] = MEM[$rB, $rC]; |
Syntax | mcp $rA, $rB, $rC |
Encoding | 0x00 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]
andMEM[$rB, $rC]
overlap - The memory range
MEM[$rA, $rC]
does not pass ownership check
MCPI: Memory copy immediate
Description | Copy bytes in memory. |
Operation | MEM[$rA, imm] = MEM[$rB, imm]; |
Syntax | mcpi $rA, $rB, imm |
Encoding | 0x00 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]
andMEM[$rB, imm]
overlap - The memory range
MEM[$rA, imm]
does not pass ownership check
MEQ: Memory equality
Description | Compare bytes in memory. |
Operation | $rA = MEM[$rB, $rD] == MEM[$rC, $rD]; |
Syntax | meq $rA, $rB, $rC, $rD |
Encoding | 0x00 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
Description | Push a set of registers from range 40..64 to the stack in order. |
Operation | tmp=$sp; $sp+=popcnt(imm)*8; MEM[tmp,$sp]=registers[40..64].mask(imm) |
Syntax | pshh imm |
Encoding | 0x00 i i i i |
Notes | The immediate value is used as a bitmask for selecting the registers. |
Panic if:
$sp + popcnt(imm)*8
overflows$sp + popcnt(imm)*8 > $hp
PSHL: Push a set of low registers to stack
Description | Push a set of registers from range 16..40 to the stack in order. |
Operation | tmp=$sp; $sp+=popcnt(imm)*8; MEM[tmp,$sp]=registers[16..40].mask(imm) |
Syntax | pshl imm |
Encoding | 0x00 i i i i |
Notes | The immediate value is used as a bitmask for selecting the registers. |
Panic if:
$sp + popcnt(imm)*8
overflows$sp + popcnt(imm)*8 > $hp
POPH: Pop a set of high registers from stack
Description | Pop to a set of registers from range 40..64 from the stack. |
Operation | tmp=$sp-popcnt(imm)*8; registers[40..64].mask(imm)=MEM[tmp,$sp] $sp-=tmp; |
Syntax | poph imm |
Encoding | 0x00 i i i i |
Notes | The immediate value is used as a bitmask for selecting the registers. |
Panic if:
$sp - popcnt(imm)*8
overflows$sp - popcnt(imm)*8 < $ssp
POPL: Pop a set of low registers from stack
Description | Pop to a set of registers from range 16..40 from the stack. |
Operation | tmp=$sp-popcnt(imm)*8; registers[16..40].mask(imm)=MEM[tmp,$sp] $sp-=tmp; |
Syntax | poph imm |
Encoding | 0x00 i i i i |
Notes | The immediate value is used as a bitmask for selecting the registers. |
Panic if:
$sp - popcnt(imm)*8
overflows$sp - popcnt(imm)*8 < $ssp
SB: Store byte
Description | The least significant byte of $rB is stored at the address $rA offset by imm . |
Operation | MEM[$rA + imm, 1] = $rB[7, 1]; |
Syntax | sb $rA, $rB, imm |
Encoding | 0x00 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
Description | The value of $rB is stored at the address $rA offset by imm . |
Operation | MEM[$rA + (imm * 8), 8] = $rB; |
Syntax | sw $rA, $rB, imm |
Encoding | 0x00 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
Description | Set $rA to the balance of asset ID at $rB for contract with ID at $rC . |
Operation | $rA = balance(MEM[$rB, 32], MEM[$rC, 32]); |
Syntax | bal $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Effects | Balance 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 intx.inputs
BHEI: Block height
Description | Get Fuel block height. |
Operation | $rA = blockheight(); |
Syntax | bhei $rA |
Encoding | 0x00 rA - - - |
Notes |
Panic if:
$rA
is a reserved register
BHSH: Block hash
Description | Get block header hash. |
Operation | MEM[$rA, 32] = blockhash($rB); |
Syntax | bhsh $rA $rB |
Encoding | 0x00 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
Description | Burn $rA coins of the $rB ID from the current contract. |
Operation | burn($rA, $rB); |
Syntax | burn $rA $rB |
Encoding | 0x00 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 IDMEM[$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, modifying tx.receiptsRoot:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Burn |
sub_id | byte[32] | Asset sub identifier MEM[$rB, $rB + 32] . |
contract_id | byte[32] | Contract ID of the current context. |
val | uint64 | Value of register $rA . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
CALL: Call contract
Description | Call contract. |
Operation | |
Syntax | call $rA $rB $rC $rD |
Encoding | 0x00 rA rB rC rD |
Effects | External 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 intx.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 IDMEM[$rC, 32]
of output with contract IDMEM[$fp, 32]
Register $rA
is a memory address from which the following fields are set (word-aligned):
bytes | type | value | description |
---|---|---|---|
32 | byte[32] | to | Contract ID to call. |
8 | byte[8] | param1 | First parameter. |
8 | byte[8] | param2 | Second 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, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Call |
from | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
to | byte[32] | Contract ID of called contract. |
amount | uint64 | Amount of coins to forward, i.e. $rB . |
asset_id | byte[32] | Asset ID of coins to forward, i.e. MEM[$rC, 32] . |
gas | uint64 | Gas to forward, i.e. min($rD, $cgas) . |
param1 | uint64 | First parameter. |
param2 | uint64 | Second parameter. |
pc | uint64 | Value of register $pc . |
is | uint64 | Value 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:
$fp = $sp
(on top of the previous call frame is the beginning of this call frame)- Set
$ssp
and$sp
to the start of the writable stack area of the call frame. - Set
$pc
and$is
to the starting address of the code. $bal = $rB
(forward coins)$cgas = $rD
or all available gas (forward gas)
This modifies the balanceRoot
field of the appropriate output(s).
CB: Coinbase address
Description | Get the coinbase address associated with the block proposer. |
Operation | MEM[$rA, 32] = coinbase(); |
Syntax | cb $rA |
Encoding | 0x00 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
Description | Copy $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 . |
Operation | MEM[$rA, $rD] = code($rB, $rC, $rD); |
Syntax | ccp $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Notes | If $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 intx.inputs
CROO: Code Merkle root
Description | Set 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 . |
Operation | MEM[$rA, 32] = coderoot(MEM[$rB, 32]); |
Syntax | croo $rA, $rB |
Encoding | 0x00 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 intx.inputs
Code root computation is defined here.
CSIZ: Code size
Description | Set $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]); |
Syntax | csiz $rA, $rB |
Encoding | 0x00 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 intx.inputs
LDC: Load code from an external contract
Description | Copy $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 . |
Operation | MEM[$ssp, $rC] = code($rA, $rB, $rC); |
Syntax | ldc $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Notes | If $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 intx.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
Description | Log an event. This is a no-op. |
Operation | log($rA, $rB, $rC, $rD); |
Syntax | log $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Notes |
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Log |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val0 | uint64 | Value of register $rA . |
val1 | uint64 | Value of register $rB . |
val2 | uint64 | Value of register $rC . |
val3 | uint64 | Value of register $rD . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
LOGD: Log data event
Description | Log an event. This is a no-op. |
Operation | logd($rA, $rB, $rC, $rD); |
Syntax | logd $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Notes |
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.LogData |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val0 | uint64 | Value of register $rA . |
val1 | uint64 | Value of register $rB . |
ptr | uint64 | Value of register $rC . |
len | uint64 | Value of register $rD . |
digest | byte[32] | Hash of MEM[$rC, $rD] . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
Logs the memory range MEM[$rC, $rD]
.
Panics if:
$rC + $rD
overflows$rA + $rD > VM_MAX_RAM
MINT: Mint new coins
Description | Mint $rA coins of the $rB ID from the current contract. |
Operation | mint($rA, $rB); |
Syntax | mint $rA $rB |
Encoding | 0x00 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 IDMEM[$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, modifying tx.receiptsRoot:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Mint |
sub_id | byte[32] | Asset sub identifier MEM[$rB, $rB + 32] . |
contract_id | byte[32] | Contract ID of the current context. |
val | uint64 | Value of register $rA . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
RETD: Return from context with data
Description | Returns from context with value MEM[$rA, $rB] . |
Operation | returndata($rA, $rB); |
Syntax | retd $rA, $rB |
Encoding | 0x00 rA rB - - |
Notes |
Panic if:
$rA + $rB
overflows$rA + $rB > VM_MAX_RAM
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.ReturnData |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
ptr | uint64 | Value of register $rA . |
len | uint64 | Value of register $rB . |
digest | byte[32] | Hash of MEM[$rA, $rB] . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
If current context is a script, append an additional receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 0 |
gas_used | uint64 | Gas 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:
$cgas = $cgas + $fp->$cgas
(add remaining context gas from previous context to current remaining context gas)
Set the return value:
$ret = $rA
$retl = $rB
Then pop the call frame and restore all registers except $ggas
, $cgas
, $ret
, $retl
and $hp
. Afterwards, set the following registers:
$pc = $pc + 4
(advance program counter from where we called)
RVRT: Revert
Description | Halt execution, reverting state changes and returning value in $rA . |
Operation | revert($rA); |
Syntax | rvrt $rA |
Encoding | 0x00 rA - - - |
Notes |
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Revert |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val | uint64 | Value of register $rA . |
pc | uint64 | Value of register $pc . |
is | uint64 | Value of register $is . |
Then append an additional receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 1 |
gas_used | uint64 | Gas consumed by the script. |
Cease VM execution and revert script effects. After a revert:
- All OutputContract outputs will have the same
balanceRoot
andstateRoot
as on initialization. - All OutputVariable outputs will have
to
,amount
, andasset_id
of zero.
SMO: Send message out
Description | Send 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. |
Operation | outputmessage(MEM[$fp, 32], MEM[$rA, 32], MEM[$rB, $rC], $rD); |
Syntax | smo $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Effects | Output 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 IDMEM[$fp, 32]
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.MessageOut |
sender | byte[32] | The address of the message sender: MEM[$fp, 32] . |
recipient | byte[32] | The address of the message recipient: MEM[$rA, 32] . |
amount | uint64 | Amount of base asset coins sent with message: $rD . |
nonce | byte[32] | The message nonce as described here. |
len | uint16 | Length of message data, in bytes: $rC . |
digest | byte[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
Description | A sequential series of 32 bytes is cleared from the current contract's state. |
Operation | STATE[MEM[$rA, 32], 32 * $rC] = None; |
Syntax | scwq $rA, $rB, $rC |
Encoding | 0x00 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
Description | A word is read from the current contract's state. |
Operation | $rA = STATE[MEM[$rC, 32]][0, 8]; |
Syntax | srw $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Effects | Storage read |
Notes | Returns zero if the state element does not exist. |
Panic if:
$rA
is a reserved register$rB
is a reserved register$rC + 32
overflows$rC + 32 > VM_MAX_RAM
$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 were set.
SRWQ: State read sequential 32 byte slots
Description | A sequential series of 32 bytes is read from the current contract's state. |
Operation | MEM[$rA, 32 * rD] = STATE[MEM[$rC, 32], 32 * rD]; |
Syntax | srwq $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Effects | Storage read |
Notes | Returns 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 were set.
SWW: State write word
Description | A word is written to the current contract's state. |
Operation | STATE[MEM[$rA, 32]][0, 8] = $rC; STATE[MEM[$rA, 32]][8, 24] = 0; |
Syntax | sww $rA $rB $rC |
Encoding | 0x00 rA rB rC - |
Effects | Storage write |
Notes |
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 false
if the storage slot was previously unset (default) and true
if the slot was set.
SWWQ: State write sequential 32 byte slots
Description | A sequential series of 32 bytes is written to the current contract's state. |
Operation | STATE[MEM[$rA, 32], 32 * $rD] = MEM[$rC, 32 * $rD]; |
Syntax | swwq $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Effects | Storage write |
Notes |
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 false
if the first storage slot was previously unset (default) and true
if the slot was set.
TIME: Timestamp at height
Description | Get timestamp of block at given height. |
Operation | $rA = time($rB); |
Syntax | time $rA, $rB |
Encoding | 0x00 rA rB - - |
Notes |
Panic if:
$rA
is a reserved register$rB
is greater than the current block height.
Gets the timestamp of the block at height $rB
. Time is in TAI64 format.
TR: Transfer coins to contract
Description | Transfer $rB coins with asset ID at $rC to contract with ID at $rA . |
Operation | transfer(MEM[$rA, 32], $rB, MEM[$rC, 32]); |
Syntax | tr $rA, $rB, $rC |
Encoding | 0x00 rA rB rC - |
Effects | Balance 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 intx.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 IDMEM[$rC, 32]
of output with contract IDMEM[$fp, 32]
$rB == 0
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.Transfer |
from | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
to | byte[32] | Contract ID of contract to transfer coins to. |
amount | uint64 | Amount of coins transferred. |
asset_id | byte[32] | asset ID of coins transferred. |
pc | uint64 | Value of register $pc . |
is | uint64 | Value 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
Description | Transfer $rC coins with asset ID at $rD to address at $rA , with output $rB . |
Operation | transferout(MEM[$rA, 32], $rB, $rC, MEM[$rD, 32]); |
Syntax | tro $rA, $rB, $rC, $rD |
Encoding | 0x00 rA rB rC rD |
Effects | Balance 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 IDMEM[$rD, 32]
of output with contract IDMEM[$fp, 32]
$rC == 0
tx.outputs[$rB].type != OutputType.Variable
tx.outputs[$rB].amount != 0
Append a receipt to the list of receipts, modifying tx.receiptsRoot
:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.TransferOut |
from | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
to | byte[32] | Address to transfer coins to. |
amount | uint64 | Amount of coins transferred. |
asset_id | byte[32] | asset ID of coins transferred. |
pc | uint64 | Value of register $pc . |
is | uint64 | Value 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
Description | The 64-byte public key (x, y) recovered from 64-byte signature starting at $rB on 32-byte message hash starting at $rC . |
Operation | MEM[$rA, 64] = ecrecover_k1(MEM[$rB, 64], MEM[$rC, 32]); |
Syntax | eck1 $rA, $rB, $rC |
Encoding | 0x00 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
Description | The 64-byte public key (x, y) recovered from 64-byte signature starting at $rB on 32-byte message hash starting at $rC . |
Operation | MEM[$rA, 64] = ecrecover_r1(MEM[$rB, 64], MEM[$rC, 32]); |
Syntax | ecr1 $rA, $rB, $rC |
Encoding | 0x00 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
Description | Verification recovered from 32-byte public key starting at $rA and 64-byte signature starting at $rB on 32-byte message hash starting at $rC . |
Operation | ed19verify(MEM[$rA, 32], MEM[$rB, 64], MEM[$rC, 32]); |
Syntax | ed19 $rA, $rB, $rC |
Encoding | 0x00 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
Description | The keccak-256 hash of $rC bytes starting at $rB . |
Operation | MEM[$rA, 32] = keccak256(MEM[$rB, $rC]); |
Syntax | k256 $rA, $rB, $rC |
Encoding | 0x00 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
Description | The SHA-2-256 hash of $rC bytes starting at $rB . |
Operation | MEM[$rA, 32] = sha256(MEM[$rB, $rC]); |
Syntax | s256 $rA, $rB, $rC |
Encoding | 0x00 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.
FLAG: Set flags
Description | Set $flag to $rA . |
Operation | $flag = $rA; |
Syntax | flag $rA |
Encoding | 0x00 rA - - - |
Notes |
Panic if:
- Any reserved flags are set
GM: Get metadata
Description | Get metadata from memory. |
Operation | Varies (see below). |
Syntax | gm $rA, imm |
Encoding | 0x00 rA imm imm imm |
Notes |
Read metadata from memory. A convenience instruction to avoid manually extracting metadata.
name | value | description |
---|---|---|
GM_IS_CALLER_EXTERNAL | 0x00001 | Get if caller is external. |
GM_GET_CALLER | 0x00002 | Get caller's contract ID. |
GM_GET_VERIFYING_PREDICATE | 0x00003 | Get index of current predicate. |
GM_GET_CHAIN_ID | 0x00004 | Get the value of CHAIN_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
Description | Get transaction fields. |
Operation | Varies (see below). |
Syntax | gtf $rA, $rB, imm |
Encoding | 0x00 rA rB i i |
Notes |
Get fields from the transaction.
name | imm | set $rA to |
---|---|---|
GTF_TYPE | 0x001 | tx.type |
GTF_SCRIPT_GAS_PRICE | 0x002 | tx.gasPrice |
GTF_SCRIPT_GAS_LIMIT | 0x003 | tx.gasLimit |
GTF_SCRIPT_MATURITY | 0x004 | tx.maturity |
GTF_SCRIPT_SCRIPT_LENGTH | 0x005 | tx.scriptLength |
GTF_SCRIPT_SCRIPT_DATA_LENGTH | 0x006 | tx.scriptDataLength |
GTF_SCRIPT_INPUTS_COUNT | 0x007 | tx.inputsCount |
GTF_SCRIPT_OUTPUTS_COUNT | 0x008 | tx.outputsCount |
GTF_SCRIPT_WITNESSES_COUNT | 0x009 | tx.witnessesCount |
GTF_SCRIPT_RECEIPTS_ROOT | 0x00A | Memory address of tx.receiptsRoot |
GTF_SCRIPT_SCRIPT | 0x00B | Memory address of tx.script |
GTF_SCRIPT_SCRIPT_DATA | 0x00C | Memory address of tx.scriptData |
GTF_SCRIPT_INPUT_AT_INDEX | 0x00D | Memory address of tx.inputs[$rB] |
GTF_SCRIPT_OUTPUT_AT_INDEX | 0x00E | Memory address of t.outputs[$rB] |
GTF_SCRIPT_WITNESS_AT_INDEX | 0x00F | Memory address of tx.witnesses[$rB] |
GTF_CREATE_GAS_PRICE | 0x010 | tx.gasPrice |
GTF_CREATE_GAS_LIMIT | 0x011 | tx.gasLimit |
GTF_CREATE_MATURITY | 0x012 | tx.maturity |
GTF_CREATE_BYTECODE_LENGTH | 0x013 | tx.bytecodeLength |
GTF_CREATE_BYTECODE_WITNESS_INDEX | 0x014 | tx.bytecodeWitnessIndex |
GTF_CREATE_STORAGE_SLOTS_COUNT | 0x015 | tx.storageSlotsCount |
GTF_CREATE_INPUTS_COUNT | 0x016 | tx.inputsCount |
GTF_CREATE_OUTPUTS_COUNT | 0x017 | tx.outputsCount |
GTF_CREATE_WITNESSES_COUNT | 0x018 | tx.witnessesCount |
GTF_CREATE_SALT | 0x019 | Memory address of tx.salt |
GTF_CREATE_STORAGE_SLOT_AT_INDEX | 0x01A | Memory address of tx.storageSlots[$rB] |
GTF_CREATE_INPUT_AT_INDEX | 0x01B | Memory address of tx.inputs[$rB] |
GTF_CREATE_OUTPUT_AT_INDEX | 0x01C | Memory address of t.outputs[$rB] |
GTF_CREATE_WITNESS_AT_INDEX | 0x01D | Memory address of tx.witnesses[$rB] |
GTF_INPUT_TYPE | 0x101 | tx.inputs[$rB].type |
GTF_INPUT_COIN_TX_ID | 0x102 | Memory address of tx.inputs[$rB].txID |
GTF_INPUT_COIN_OUTPUT_INDEX | 0x103 | tx.inputs[$rB].outputIndex |
GTF_INPUT_COIN_OWNER | 0x104 | Memory address of tx.inputs[$rB].owner |
GTF_INPUT_COIN_AMOUNT | 0x105 | tx.inputs[$rB].amount |
GTF_INPUT_COIN_ASSET_ID | 0x106 | Memory address of tx.inputs[$rB].asset_id |
GTF_INPUT_COIN_TX_POINTER | 0x107 | Memory address of tx.inputs[$rB].txPointer |
GTF_INPUT_COIN_WITNESS_INDEX | 0x108 | tx.inputs[$rB].witnessIndex |
GTF_INPUT_COIN_MATURITY | 0x109 | tx.inputs[$rB].maturity |
GTF_INPUT_COIN_PREDICATE_LENGTH | 0x10A | tx.inputs[$rB].predicateLength |
GTF_INPUT_COIN_PREDICATE_DATA_LENGTH | 0x10B | tx.inputs[$rB].predicateDataLength |
GTF_INPUT_COIN_PREDICATE | 0x10C | Memory address of tx.inputs[$rB].predicate |
GTF_INPUT_COIN_PREDICATE_DATA | 0x10D | Memory address of tx.inputs[$rB].predicateData |
GTF_INPUT_COIN_PREDICATE_GAS_USED | 0x10E | tx.inputs[$rB].predicateGasUsed |
GTF_INPUT_CONTRACT_TX_ID | 0x10F | Memory address of tx.inputs[$rB].txID |
GTF_INPUT_CONTRACT_OUTPUT_INDEX | 0x110 | tx.inputs[$rB].outputIndex |
GTF_INPUT_CONTRACT_BALANCE_ROOT | 0x111 | Memory address of tx.inputs[$rB].balanceRoot |
GTF_INPUT_CONTRACT_STATE_ROOT | 0x112 | Memory address of tx.inputs[$rB].stateRoot |
GTF_INPUT_CONTRACT_TX_POINTER | 0x113 | Memory address of tx.inputs[$rB].txPointer |
GTF_INPUT_CONTRACT_CONTRACT_ID | 0x114 | Memory address of tx.inputs[$rB].contractID |
GTF_INPUT_MESSAGE_SENDER | 0x115 | Memory address of tx.inputs[$rB].sender |
GTF_INPUT_MESSAGE_RECIPIENT | 0x116 | Memory address of tx.inputs[$rB].recipient |
GTF_INPUT_MESSAGE_AMOUNT | 0x117 | tx.inputs[$rB].amount |
GTF_INPUT_MESSAGE_NONCE | 0x118 | Memory address of tx.inputs[$rB].nonce |
GTF_INPUT_MESSAGE_WITNESS_INDEX | 0x119 | tx.inputs[$rB].witnessIndex |
GTF_INPUT_MESSAGE_DATA_LENGTH | 0x11A | tx.inputs[$rB].dataLength |
GTF_INPUT_MESSAGE_PREDICATE_LENGTH | 0x11B | tx.inputs[$rB].predicateLength |
GTF_INPUT_MESSAGE_PREDICATE_DATA_LENGTH | 0x11C | tx.inputs[$rB].predicateDataLength |
GTF_INPUT_MESSAGE_DATA | 0x11D | Memory address of tx.inputs[$rB].data |
GTF_INPUT_MESSAGE_PREDICATE | 0x11E | Memory address of tx.inputs[$rB].predicate |
GTF_INPUT_MESSAGE_PREDICATE_DATA | 0x11F | Memory address of tx.inputs[$rB].predicateData |
GTF_INPUT_MESSAGE_PREDICATE_GAS_USED | 0x120 | tx.inputs[$rB].predicateGasUsed |
GTF_OUTPUT_TYPE | 0x201 | tx.outputs[$rB].type |
GTF_OUTPUT_COIN_TO | 0x202 | Memory address of tx.outputs[$rB].to |
GTF_OUTPUT_COIN_AMOUNT | 0x203 | tx.outputs[$rB].amount |
GTF_OUTPUT_COIN_ASSET_ID | 0x204 | Memory address of tx.outputs[$rB].asset_id |
GTF_OUTPUT_CONTRACT_INPUT_INDEX | 0x205 | tx.outputs[$rB].inputIndex |
GTF_OUTPUT_CONTRACT_BALANCE_ROOT | 0x206 | Memory address of tx.outputs[$rB].balanceRoot |
GTF_OUTPUT_CONTRACT_STATE_ROOT | 0x207 | Memory address of tx.outputs[$rB].stateRoot |
GTF_OUTPUT_CONTRACT_CREATED_CONTRACT_ID | 0x208 | Memory address of tx.outputs[$rB].contractID |
GTF_OUTPUT_CONTRACT_CREATED_STATE_ROOT | 0x209 | Memory address of tx.outputs[$rB].stateRoot |
GTF_WITNESS_DATA_LENGTH | 0x301 | tx.witnesses[$rB].dataLength |
GTF_WITNESS_DATA | 0x302 | Memory address of tx.witnesses[$rB].data |
Panic if:
$rA
is a reserved registerimm
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
andOutputVariable
count asOutputCoin
)
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.
name | type | description |
---|---|---|
prevRoot | byte[32] | Merkle root of all previous consensus header hashes (i.e. not including this block). |
height | uint32 | Height of this block. |
timestamp | uint64 | Time this block was created, in TAI64 format. |
applicationHash | byte[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
- Test Empty Root
- Test Update 1
- Test Update 2
- Test Update 3
- Test Update 5
- Test Update 10
- Test Update 100
- Test Update With Repeated Inputs
- Test Update Overwrite Key
- Test Update Union
- Test Update Sparse Union
- Test Update With Empty Data
- Test Update With Empty Data Performs Delete
- Test Update 1 Delete 1
- Test Update 2 Delete 1
- Test Update 10 Delete 5
- Test Delete Non-existent Key
- Test Interleaved Update Delete
- 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:
- Update the empty tree with
(K, D)
where leaf keyK = Sum(0u32)
(32 bytes) and leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(1u32)
and leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(1u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(2u32)
and leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(1u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(2u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(3u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(4u32)
and leaf dataD = 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:
- For each
i
in0..10
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = 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:
- For each
i
in0..100
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree again with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = 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:
- For each
i
in0..5
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in10..15
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in20..25
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = 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:
- For each
i
in0..5
, update the tree with(K, D)
, where leaf keyK = Sum(i * 2)
and leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and empty leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(0u32)
and empty leaf dataD = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Delete
(K)
from the tree, where leaf keyK = 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:
- Update the empty tree with
(K, D)
, where leaf keyK = Sum(0u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Update the tree with
(K, D)
, where leaf keyK = Sum(1u32)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Delete
(K)
from the tree, where leaf keyK = 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:
- For each
i
in0..10
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in5..10
, delete(K)
from the tree, where leaf keyK = 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:
- For each
i
in0..5
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - Delete
(K)
from the tree, where leaf keyK = 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:
- For each
i
in0..10
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in5..15
, delete(K)
from the tree, where leaf keyK = Sum(i)
from the tree - For each
i
in10..20
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in15..25
, delete(K)
from the tree, where leaf keyK = Sum(i)
from the tree - For each
i
in20..30
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in25..35
, delete(K)
from the tree, where leaf keyK = 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:
- For each
i
in0..10
, update the tree with(K, D)
, where leaf keyK = Sum(i)
and leaf dataD = b"DATA"
(bytes, UTF-8) - For each
i
in0..5
, delete(K)
from the tree, where leaf keyK = 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