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.
Consensus Parameters
name | type | description |
---|---|---|
GAS_PER_BYTE | uint64 | Gas charged per byte of the transaction. |
GAS_PRICE_FACTOR | uint64 | Unit factor for gas price. |
MAX_GAS_PER_TX | uint64 | Maximum gas per transaction. |
MAX_INPUTS | uint64 | Maximum number of inputs. |
MAX_OUTPUTS | uint64 | 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 | uint64 | Maximum length of message data, in bytes. |
MAX_STORAGE_SLOTS | uint64 | Maximum number of initial storage slots. |
MAX_TRANSACTION_SIZE | uint64 | Maximum size of a transaction, in bytes. |
MAX_WITNESSES | uint64 | Maximum number of witnesses. |
MAX_BYTECODE_SUBSECTIONS | uint64 | Maximum number of bytecode subsections. |
CHAIN_ID | uint64 | A unique per-chain identifier. |
BASE_ASSET_ID | bytes32 | The base asset of the chain. |
PRIVILEGED_ADDRESS | bytes32 | The privileged address of the network who can perform upgrade. |
Transaction
enum TransactionType : uint8 {
Script = 0,
Create = 1,
Mint = 2,
Upgrade = 3,
Upload = 4,
}
name | type | description |
---|---|---|
type | TransactionType | Transaction type. |
data | One of TransactionScript , TransactionCreate , TransactionMint , TransactionUpgrade , or TransactionUpload | Transaction data. |
Given helper max_gas()
returns the maximum gas that the transaction can use.
Given helper count_ones()
that returns the number of ones in the binary representation of a field.
Given helper count_variants()
that returns the number of variants in an enum.
Given helper sum_variants()
that sums all variants of an enum.
Transaction is invalid if:
type
is not validTransactionType
valueinputsCount > MAX_INPUTS
outputsCount > MAX_OUTPUTS
witnessesCount > MAX_WITNESSES
size > MAX_TRANSACTION_SIZE
. The size of a transaction is calculated as the sum of the sizes of its static and dynamic parts, as determined by canonical serialization.- No inputs are of type
InputType.Coin
orInputType.Message
withinput.dataLength
== 0 - More than one output is of type
OutputType.Change
for any asset ID in the input set - More than one output is of type
OutputType.Change
with identicalasset_id
fields. - Any output is of type
OutputType.Change
for any asset ID not in the input set - More than one input of type
InputType.Coin
for any Coin ID in the input set - More than one input of type
InputType.Contract
for any Contract ID in the input set - More than one input of type
InputType.Message
for any Message ID in the input set - if
type != TransactionType.Mint
max_gas(tx) > MAX_GAS_PER_TX
- No policy of type
PolicyType.MaxFee
is set count_ones(policyTypes) > count_variants(PolicyType)
policyTypes > sum_variants(PolicyType)
len(policies) > count_ones(policyTypes)
When serializing a transaction, fields are serialized as follows (with inner structs serialized recursively):
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 |
---|---|---|
scriptGasLimit | uint64 | Gas limits the script execution. |
receiptsRoot | byte[32] | Merkle root of receipts. |
scriptLength | uint64 | Script length, in instructions. |
scriptDataLength | uint64 | Length of script input data, in bytes. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
script | byte[] | Script to execute. |
scriptData | byte[] | Script input data (parameters). |
policies | Policy[] | List of policies, sorted by PolicyType . |
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)
Note: when signing a transaction,
receiptsRoot
is set to zero.Note: when verifying a predicate or executing a script,
receiptsRoot
is initialized to zero.
The receipts root receiptsRoot
is the root of the binary Merkle tree of receipts. If there are no receipts, its value is set to the root of the empty tree, i.e. 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
.
TransactionCreate
name | type | description |
---|---|---|
bytecodeWitnessIndex | uint16 | Witness index of contract bytecode to create. |
salt | byte[32] | Salt. |
storageSlotsCount | uint64 | Number of storage slots to initialize. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
storageSlots | (byte[32], byte[32]])[] | List of storage slots to initialize (key, value). |
policies | Policy[] | List of policies. |
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 input uses non-base asset.
- Any output is of type
OutputType.Contract
orOutputType.Variable
orOutputType.Message
- Any output is of type
OutputType.Change
with non-baseasset_id
- It does not have exactly one output of type
OutputType.ContractCreated
tx.data.witnesses[bytecodeWitnessIndex].dataLength > CONTRACT_MAX_SIZE
bytecodeWitnessIndex >= tx.witnessesCount
- The keys of
storageSlots
are not in ascending lexicographic order - The computed contract ID (see below) is not equal to the
contractID
of the 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 | InputContract | The contract UTXO that assets are minted to. |
outputContract | OutputContract | 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. |
gasPrice | uint64 | The gas price to be used in calculating all fees for transactions on block |
Transaction is invalid if:
txPointer
is zero or doesn't match the block.outputContract.inputIndex
is not zero
TransactionUpgrade
The Upgrade
transaction allows upgrading either consensus parameters or state transition function used by the network to produce future blocks. The Upgrade Purpose
type defines the purpose of the upgrade.
The Upgrade
transaction is chargeable, and the sender should pay for it. Transaction inputs should contain only base assets.
Only the privileged address from ConsensusParameters
can upgrade the network. The privileged address can be either a real account or a predicate.
When the upgrade type is UpgradePurposeType.ConsensusParameters
serialized consensus parameters are available in the witnesses and the Upgrade
transaction is self-contained because it has all the required information.
When the upgrade type is UpgradePurposeType.StateTransition
, the bytecodeRoot
field contains the Merkle root of the new bytecode of the state transition function. The bytecode should already be available on the blockchain at the upgrade point; otherwise, the upgrade will fail. The bytecode can be part of the genesis block or can be uploaded via the TransactionUpload
transaction.
The block header contains information about which versions of consensus parameters and state transition function are used to produce a block, and the Upgrade
transaction defines behavior corresponding to the version. When the block executes the Upgrade
transaction, it defines new behavior for either BlockHeader.consensusParametersVersion + 1
or BlockHeader.stateTransitionBytecodeVersion + 1
(it depends on the purpose of the upgrade).
When the Upgrade
transaction is included in the block, it doesn't affect the current block execution. Since behavior is now defined, the inclusion of the Upgrade
transaction allows the production of the next block with a new version. The block producer can still continue to use the previous version and start using a new version later unless the state transition function forbids it.
The behavior is set once per version. It is forbidden to override the behavior of the network. Each behavior should have its own version. The version should grow monotonically without jumps.
The BlockHeader.consensusParametersVersion
and BlockHeader.stateTransitionBytecodeVersion
are independent and can grow at different speeds.
name | type | description |
---|---|---|
upgradePurpose | UpgradePurpose | The purpose of the upgrade. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
policies | Policy[] | List of policies. |
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 input uses non-base asset.
- Any output is of type
OutputType.Contract
orOutputType.Variable
orOutputType.Message
orOutputType.ContractCreated
- Any output is of type
OutputType.Change
with non-baseasset_id
- No input where
InputType.Message.owner == PRIVILEGED_ADDRESS
orInputType.Coint.owner == PRIVILEGED_ADDRESS
- The
UpgradePurpose
is invalid
TransactionUpload
The Upload
transaction allows the huge bytecode to be divided into subsections and uploaded slowly to the chain. The Binary Merkle root built on top of subsections is an identifier of the bytecode.
Each transaction uploads a subsection of the code and must contain proof of connection to the root. All subsections should be uploaded sequentially, which allows the concatenation of previously uploaded subsections with new subsection. The bytecode is considered final when the last subsection is uploaded, and future Upload
transactions with the same root
fields should be rejected.
When the bytecode is completed it can be used to upgrade the network.
The size of each subsection can be arbitrary; the only limit is the maximum number of subsections allowed by the network. The combination of the transaction gas limit and the number of subsections limits the final maximum size of the bytecode.
name | type | description |
---|---|---|
root | byte[32] | The root of the Merkle tree is created over the bytecode. |
witnessIndex | uint16 | The witness index of the subsection of the bytecode. |
subsectionIndex | uint16 | The index of the subsection of the bytecode. |
subsectionsNumber | uint16 | The total number of subsections on which bytecode was divided. |
proofSetCount | uint16 | Number of Merkle nodes in the proof. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
proofSet | byte[32][] | The proof set of Merkle nodes to verify the connection of the subsection to the root . |
policies | Policy[] | List of policies. |
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 input uses non-base asset.
- Any output is of type
OutputType.Contract
orOutputType.Variable
orOutputType.Message
orOutputType.ContractCreated
- Any output is of type
OutputType.Change
with non-baseasset_id
witnessIndex >= tx.witnessesCount
subsectionIndex
>=subsectionsNumber
subsectionsNumber > MAX_BYTECODE_SUBSECTIONS
- The Binary Merkle tree root calculated from
(witnesses[witnessIndex], subsectionIndex, subsectionsNumber, proofSet)
is not equal to theroot
. Root calculation is affected by all fields, so modification of one of them invalidates the proof.
UpgradePurposeType
enum UpgradePurposeType : uint8 {
ConsensusParameters = 0,
StateTransition = 1,
}
name | type | description |
---|---|---|
type | UpgradePurposeType | Type of upgrade purpose. |
data | One of ConsensusParameters , StateTransition | Upgrade purposes. |
Transaction is invalid if:
type
is not validUpgradePurposeType
value`
ConsensusParameters
name | type | description |
---|---|---|
witnessIndex | uint16 | Index of witness that contains a serialized(with postcard) consensus parameters. |
checksum | byte[32] | The hash of the serialized consensus parameters. |
Given helper deserialize_consensus_parameters()
that deserializes the consensus parameters from a witness by using postcard algorithm.
Transaction is invalid if:
witnessIndex >= tx.witnessesCount
checksum != sha256(tx.data.witnesses[witnessIndex])
deserialize_consensus_parameters(tx.data.witnesses[witnessIndex])
returns an error.
StateTransition
name | type | description |
---|---|---|
bytecodeRoot | byte[32] | The root of the new bytecode of the state transition function. |
Policy
// index using powers of 2 for efficient bitmasking
enum PolicyType : uint32 {
Tip = 1,
WitnessLimit = 2,
Maturity = 4,
MaxFee = 8,
}
name | type | description |
---|---|---|
data | One of Tip , WitnessLimit , or Maturity | Policy data. |
Tip
name | type | description |
---|---|---|
tip | uint64 | Additional, optional fee in BASE_ASSET to incentivize block producer to include transaction |
WitnessLimit
name | type | description |
---|---|---|
witnessLimit | uint64 | The maximum amount of witness data allowed for the transaction |
Given helper len()
that returns the number of bytes of a field.
Transaction is invalid if:
len(tx.witnesses) > witnessLimit
Maturity
name | type | description |
---|---|---|
maturity | uint32 | Block until which the transaction cannot be included. |
Transaction is invalid if:
blockheight() < maturity
MaxFee
name | type | description |
---|---|---|
max_fee | uint64 | Required policy to specify the maximum fee payable by this transaction using BASE_ASSET . This is used to check transactions before the actual gas_price is known. |
Transaction is invalid if:
max_fee > sum_inputs(tx, BASE_ASSET_ID) - sum_outputs(tx, BASE_ASSET_ID)
max_fee < max_fee(tx, BASE_ASSET_ID, gas_price)
Input
enum InputType : uint8 {
Coin = 0,
Contract = 1,
Message = 2,
}
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 | uint16 | 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 | uint16 | Index of witness that authorizes spending the coin. |
predicateGasUsed | uint64 | Gas used by predicate. |
predicateLength | uint64 | Length of predicate, in instructions. |
predicateDataLength | uint64 | 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
Note: when signing a transaction,
txPointer
andpredicateGasUsed
are set to zero.Note: when verifying and estimating a predicate or executing a script,
txPointer
andpredicateGasUsed
are initialized to zero.
The predicate root is computed here.
InputContract
name | type | description |
---|---|---|
txID | byte[32] | Hash of transaction. |
outputIndex | uint16 | 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 or executing a script,
txID
,outputIndex
,balanceRoot
,stateRoot
, andtxPointer
are initialized to zero.
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 | uint16 | Index of witness that authorizes spending the coin. |
predicateGasUsed | uint64 | Gas used by predicate execution. |
dataLength | uint64 | Length of message data, in bytes. |
predicateLength | uint64 | Length of predicate, in instructions. |
predicateDataLength | uint64 | 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 | uint16 | 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 or executing a script,
balanceRoot
andstateRoot
are initialized to zero.
The balance root balanceRoot
is the root of the SMT of balance leaves. Each balance is a uint64
, keyed by asset ID (a byte[32]
).
The state root stateRoot
is the root of the SMT of storage slots. Each storage slot is a byte[32]
, keyed by a byte[32]
.
OutputChange
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 | uint64 | 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 ++ root(input.predicate))
, where root
is the Merkle root of
the binary Merkle tree each leaf being 16KiB of instructions.
If the bytecode is not a multiple of 16 KiB, the final leaf should be zero-padded rounding up to the nearest multiple of 8 bytes.
Transaction ID
The transaction ID (also called transaction hash) of a transaction is computed as
the hash of CHAIN_ID
and the
serialized transaction with fields zeroed out for signing
(see different inputs and outputs for which fields are set to zero), and without witness data. In other words, only
all non-witness data is hashed.
sha256(CHAIN_ID ++ serialized_tx(tx))
UTXO ID
Coin ID
Is represented as an outpoint: a pair of transaction ID as byte[32]
and output index as a uint16
.
Message ID
The ID of a message is computed as the hash of:
- 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 uint16
(with canonical encoding): hash(byte[32] ++ canonical(uint16))
.
Fee ID
The UTXO ID of collected fees in a block is the block height as a 32-byte big-endian unsigned integer (i.e. the first byte of the 32-byte array is the most significant byte, and so on).
Protocol
Transaction Validity
- Transaction Life Cycle
- Access Lists
- VM Precondition Validity Rules
- Predicate Verification
- Script Execution
- VM Postcondition Validity Rules
Transaction Life Cycle
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
whereinput.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 assetId
in the input and output set:
def gas_to_fee(gas, gasPrice) -> int:
"""
Converts gas units into a fee amount
"""
return ceil(gas * gasPrice / GAS_PRICE_FACTOR)
def sum_data_messages(tx, assetId) -> int:
"""
Returns the total balance available from messages containing data
"""
total: int = 0
if assetId == 0:
for input in tx.inputs:
if input.type == InputType.Message and input.dataLength > 0:
total += input.amount
return total
def sum_inputs(tx, assetId) -> int:
total: int = 0
for input in tx.inputs:
if input.type == InputType.Coin and input.assetId == assetId:
total += input.amount
elif input.type == InputType.Message and assetId == 0 and input.dataLength == 0:
total += input.amount
return total
def transaction_size_gas_fees(tx) -> int:
"""
Computes the intrinsic gas cost of a transaction based on size in bytes
"""
return size(tx) * GAS_PER_BYTE
def minted(tx, assetId) -> int:
"""
Returns any minted amounts by the transaction
"""
if tx.type != TransactionType.Mint or assetId != tx.mintAssetId:
return 0
return tx.mint_amount
def sum_outputs(tx, assetId) -> int:
total: int = 0
for output in tx.outputs:
if output.type == OutputType.Coin and output.assetId == assetId:
total += output.amount
return total
def input_gas_fees(tx) -> int:
"""
Computes the intrinsic gas cost of verifying input utxos
"""
total: int = 0
witnessIndices = set()
for input in tx.inputs:
if input.type == InputType.Coin or input.type == InputType.Message:
# add fees allocated for predicate execution
if input.predicateLength == 0:
# notate witness index if input is signed
witnessIndices.add(input.witnessIndex)
else:
# add intrinsic gas cost of predicate merkleization based on number of predicate bytes
total += contract_code_root_gas_fee(input.predicateLength)
total += input.predicateGasUsed
# add intrinsic cost of vm initialization
total += vm_initialization_gas_fee()
# add intrinsic cost of verifying witness signatures
total += len(witnessIndices) * eck1_recover_gas_fee()
return total
def metadata_gas_fees(tx) -> int:
"""
Computes the intrinsic gas cost of processing transaction outputs
The `contract_code_root_gas_fee`, `sha256_gas_fee`, and `contract_state_root_gas_fee`
are based on the benchmarked gas costs of these operations.
Consensus parameters contain definitions of gas costs for all operations and opcodes in the network.
"""
total: int = 0
if tx.type == TransactionType.Create:
for output in tx.outputs:
if output.type == OutputType.OutputContractCreated:
# add intrinsic cost of calculating the code root based on the size of the contract bytecode
total += contract_code_root_gas_fee(tx.witnesses[tx.bytecodeWitnessIndex].dataLength)
# add intrinsic cost of calculating the state root based on the number of sotrage slots
total += contract_state_root_gas_fee(tx.storageSlotCount)
# add intrinsic cost of calculating the contract id
# size = 4 byte seed + 32 byte salt + 32 byte code root + 32 byte state root
total += sha256_gas_fee(100)
elif tx.type == TransactionType.Upgrade:
if tx.upgradePurpose.type == UpgradePurposeType.ConsensusParameters:
# add intrinsic cost of calculating the consensus parameters hash
total += sha256_gas_fee(size(tx.witnesses[tx.upgradePurpose.witnessIndex].data))
elif tx.type == TransactionType.Upload:
# add intrinsic cost of calculating the root based on the number of bytecode subsections
total += contract_state_root_gas_fee(tx.subsectionsNumber)
# add intrinsic cost of hashing the subsection for verification of the connection with Binary Merkle tree root
total += sha256_gas_fee(size(tx.witnesses[tx.witnessIndex]))
if tx.type != TransactionType.Mint:
# add intrinsic cost of calculating the transaction id
total += sha256_gas_fee(size(tx))
return total
def intrinsic_gas_fees(tx) -> int:
"""
Computes intrinsic costs for a transaction
"""
fees: int = 0
# add the cost of initializing a vm for the script
if tx.type == TransactionType.Create or tx.type == TransactionType.Script:
fees += vm_initialization_gas_fee()
fees += metadata_gas_fees(tx)
fees += intrinsic_input_gas_fees(tx)
return fees
def min_gas(tx) -> int:
"""
Comutes the minimum amount of gas required for a transaction to begin processing.
"""
gas = transaction_size_gas_fees(tx) + intrinsic_gas_fees(tx)
if tx.type == TransactionType.Upload
# charge additionally for storing bytecode on chain
gas += transaction_size_gas_fees(size(tx.witnesses[tx.witnessIndex]))
return gas
def max_gas(tx) -> int:
"""
Computes the amount of gas required to process a transaction.
"""
gas = min_gas(tx)
gas = gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE
if tx.type == TransactionType.Script:
gas += tx.gasLimit
return gas
def maxFee(tx, assetId, gasPrice) -> int:
"""
Computes the maximum potential amount of fees that may need to be charged to process a transaction.
"""
maxGas = max_gas(tx)
feeBalance = gas_to_fee(maxGas, gasPrice)
# Only base asset can be used to pay for gas
if assetId == 0:
return feeBalance
else:
return 0
def available_balance(tx, assetId) -> int:
"""
Make the data message balance available to the script
"""
availableBalance = sum_inputs(tx, assetId) + sum_data_messages(tx, assetId) + minted(tx, assetId)
return availableBalance
def unavailable_balance(tx, assetId) -> int:
sentBalance = sum_outputs(tx, assetId)
# Total fee balance
feeBalance = tx.policies.max_fee
# Only base asset can be used to pay for gas
if assetId == 0:
return sentBalance + feeBalance
return sentBalance
# The sum_data_messages total is not included in the unavailable_balance since it is spendable as long as there
# is enough base asset amount to cover gas costs without using data messages. Messages containing data can't
# cover gas costs since they are retryable.
return available_balance(tx, assetId) >= (unavailable_balance(tx, assetId) + sum_data_messages(tx, assetId))
Valid Signatures
def address_from(pubkey: bytes) -> bytes:
return sha256(pubkey)[0:32]
for input in tx.inputs:
if (input.type == InputType.Coin or input.type == InputType.Message) and input.predicateLength == 0:
# ECDSA signatures must be 64 bytes
if tx.witnesses[input.witnessIndex].dataLength != 64:
return False
# Signature must be from owner
if address_from(ecrecover_k1(txhash(), tx.witnesses[input.witnessIndex].data)) != input.owner:
return False
return True
Signatures and signature verification are specified here.
The transaction hash is computed as defined here.
Predicate Verification
For each input of type InputType.Coin
or InputType.Message
, and predicateLength > 0
, verify its predicate.
Script Execution
Given transaction tx
, the following checks must pass:
If tx.scriptLength == 0
, there is no script and the transaction defines a simple balance transfer, so no further checks are required.
If tx.scriptLength > 0
, the script must be executed. For each asset ID assetId
in the input set, the free balance available to be moved around by the script and called contracts is freeBalance[assetId]
. The initial message balance available to be moved around by the script and called contracts is messageBalance
:
freeBalance[assetId] = available_balance(tx, assetId) - unavailable_balance(tx, assetId)
messageBalance = sum_data_messages(tx, 0)
Once the free balances are computed, the script is executed. After execution, the following is extracted:
- 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.
size(tx)
encompasses the entire transaction serialized according to the transaction format, including witness data.
This ensures every byte of block space either on Fuel or corresponding DA layer can be accounted for.
If the transaction as included in a block does not match this final transaction, the block is invalid.
Fees
The cost of a transaction can be described by:
def cost(tx, gasPrice) -> int:
return gas_to_fee(min_gas(tx) + tx.gasLimit - unspentGas, gasPrice)
where:
min_gas(tx)
is the minimum cost of the transaction in gas, including intrinsic gas fees incurred from:- The number of bytes comprising the transaction
- Processing inputs, including predicates
- Processing outputs
- VM initialization
unspentGas
is the amount gas left over after intrinsic fees and execution of the transaction, extracted from the$ggas
register. Converting unspent gas to a fee describes how much "change" is left over from the user's payment; the block producer collects this unspent gas as reward.gas_to_fee
is a function that converts gas to a concrete fee based on a given gas price.
Fees incurred by transaction processing outside the context of execution are collectively referred to as intrinsic fees. Intrinsic fees include the cost of storing the transaction, calculated on a per-byte basis, the cost of processing inputs and outputs, including predicates and signature verification, and initialization of the VM prior to any predicate or script execution. Because intrinsic fees are independent of execution, they can be determined a priori and represent the bare minimum cost of the transaction.
A naturally occurring result of a variable gas limit is the concept of minimum and maximum fees. The minimum fee is, thus, the exact fee required to pay the fee balance, while the maximum fee is the minimum fee plus the gas limit:
min_gas = min_gas(tx)
max_gas = min_gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE + tx.gasLimit
min_fee = gas_to_fee(min_gas, gasPrice)
max_fee = gas_to_fee(max_gas, gasPrice)
The cost of the transaction cost(tx)
must lie within the range defined by [min_fee
, max_fee
]. min_gas
is defined as the sum of all intrinsic costs of the transaction known prior to execution. The definition of max_gas
illustrates that the delta between minimum gas and maximum gas is the sum of:
- The remaining allocation of witness bytes, converted to gas
- The user-defined
tx.gasLimit
Note that gasLimit
applies to transactions of type Script
. gasLimit
is not applicable for transactions of type Create
and is defined to equal 0
in the above formula.
A transaction cost cost(tx)
, in gas, greater than max_gas
is invalid and must be rejected; this signifies that the user must provide a higher gas limit for the given transaction. min_fee
is the minimum reward the producer is guaranteed to collect, and max_fee
is the maximum reward the producer is potentially eligible to collect. In practice, the user is always charged intrinsic fees; thus, unspentGas
is the remainder of max_gas
after intrinsic fees and the variable cost of execution. Calculating a conversion from unspentGas
to an unspent fee describes the reward the producer will collect in addition to min_fee
.
VM Postcondition Validity Rules
This section defines VM postcondition validity rules for transactions: the requirements for a transaction to be valid after it has been executed.
Given transaction tx
, state state
, and contract set contracts
, the following checks must pass.
Correct Change
If change outputs are present, they must have:
- if the transaction does not revert;
- if the asset ID is
0
; anamount
ofunspentBalance + floor((unspentGas * gasPrice) / GAS_PRICE_FACTOR)
- otherwise; an
amount
of the unspent free balance for that asset ID after VM execution is complete
- if the asset ID is
- if the transaction reverts;
- if the asset ID is
0
; anamount
of the initial free balance plus(unspentGas * 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 theassetId
that fees are paid in (assetId == 0
).
The minted amount of the coinbase transaction intrinsically increases the balance corresponding to the inputContract
.
This means the balance of mintAssetId
is directly increased by mintAmount
on the input contract,
without requiring any VM execution. Compared to coin outputs, intrinsically increasing a contract balance to collect
coinbase amounts prevents the accumulation of dust during low-usage periods.
Cryptographic Primitives
Hashing
All hashing is done with SHA-2-256 (also known as SHA-256), defined in FIPS 180-4.
HashDigest
Output of the hashing function. Exactly 256 bits (32 bytes) long.
Merkle Trees
Two Merkle tree structures are used: a Binary Merkle Tree (to commit to bytecode) and a Sparse Merkle Tree (to commit to contract storage, i.e. state).
Binary Merkle Tree
Binary Merkle trees are constructed in the same fashion as described in Certificate Transparency (RFC-6962), except for using a different hashing function. Leaves are hashed once to get leaf node values and internal node values are the hash of the concatenation of their children (either leaf nodes or other internal nodes).
Nodes contain a single field:
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, with wrappers in Go and Rust.
Public keys are encoded in uncompressed form, as the concatenation of the x
and y
values. No prefix is needed to distinguish between encoding schemes as this is the only encoding supported.
Deterministic signatures (RFC-6979) should be used when signing, but this is not enforced at the protocol level as it cannot be.
Signatures are represented as the r
and s
(each 32 bytes), and v
(1-bit) values of the signature. r
and s
take on their usual meaning (see: SEC 1, 4.1.3 Signing Operation), while v
is used for recovering the public key from a signature more quickly (see: SEC 1, 4.1.6 Public Key Recovery Operation). Only low-s
values in signatures are valid (i.e. s <= secp256k1.n//2
); s
can be replaced with -s mod secp256k1.n
during the signing process if it is high. Given this, the first bit of s
will always be 0
, and can be used to store the 1-bit v
value.
v
represents the parity of the Y
component of the point, 0
for even and 1
for odd. The X
component of the point is assumed to always be low, since the possibility of it being high is negligible.
Putting it all together, the encoding for signatures is:
| 32 bytes || 32 bytes |
[256-bit r value][1-bit v value][255-bit s value]
This encoding scheme is derived from EIP 2098: Compact Signature Representation.
EdDSA Public-Key Cryptography
Ed25519 is supported for use by applications built on Fuel. Edwards curve operations are performed by the ed25519-dalek Rust library.
Public keys are encoded in compressed form as specified by the Ed25519 format RFC-8032 5.1.5. Point compression is performed by replacing the most significant bit in the final octet of the y
coordinate with the sign bit from the x
coordinate:
#![allow(unused)] fn main() { let mut pk = y; pk ^= x.is_negative().unwrap_u8() << 7; }
Public keys are required to be strong enough to prevent malleability, and are checked for weakness during signature verification.
Signatures are 64 bytes, represented as the concatenation of R
(32 bytes) and S
(32 bytes) Where R
and S
are defined in RFC-8032 5.1.6.
Signatures must conform to strict verification requirements to avoid malleability concerns. While this is not part of the original Ed25519 specification, it has become a growing concern especially in cryptocurrency applications.
JSON Format for Contract Storage Initializers
Contracts can request that certain storage slots are initialized to specific values. These initialized slots are represented in JSON format as an array where each element represents a storage slot and has the following properties:
"key"
: String, a 32-byte key for a given storage slot;"value"
: String, a 32-byte value that initializes the slot;
For instance, the following is a JSON object that requests that the 3 storage slots with keys 0x11..11
, 0x22..22
, and 0x33..33
, are respectively initialized to the values indicated.
[
{
"key": "0x1111111111111111111111111111111111111111111111111111111111111111",
"value": "0x1010101010101010101010101010101010101010101010101010101010101010"
},
{
"key": "0x2222222222222222222222222222222222222222222222222222222222222222",
"value": "0x2020202020202020202020202020202020202020202020202020202020202020"
},
{
"key": "0x3333333333333333333333333333333333333333333333333333333333333333",
"value": "0x0303030303030303030303030303030303030303030303030303030303030303"
},
]
Block Header
Application Header
The application header is a network-agnostic block header. Different networks may wrap the application header in a consensus header, depending on their consensus protocol.
name | type | description |
---|---|---|
da_height | uint64 | Height of the data availability layer up to which (inclusive) input messages are processed. |
consensusParametersVersion | uint32 | The version of the consensus parameters used to execute this block. |
stateTransitionBytecodeVersion | uint32 | The version of the state transition bytecode used to execute this block. |
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
Version 0
:warning: This version is being deprecated for Version 1 (see below).
When crafting transaction script data, you must encode the arguments you wish to pass to the script.
Types
These are the available types that can be encoded in the ABI:
- Unsigned integers:
u8
, 8 bits.u16
, 16 bits.u32
, 32 bits.u64
, 64 bits.u128
, 128 bits.u256
, 256 bits.
- Boolean:
bool
, 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
- Vectors
- Tuples
These types are encoded in-place. Here's how to encode them. We define enc(X)
the encoding of the type X
.
Unsigned Integers
u<M>
where M
is either 8, 16, 32, 64, 128 or 256 bits.
enc(X)
is the big-endian (i.e. right-aligned) representation of X
left-padded with zero-bytes.
- In the case of
M
being 8, 16, 32 or 64, total length must be 8 bytes. - If
M
is 128, total length must be 16 bytes. - If
M
is 256, total length must be 32 bytes.
Note: since all integer values are unsigned, there is no need to preserve the sign when extending/padding; padding with only zeroes is sufficient._
Example:
Encoding 42
yields: 0x000000000000002a
, which is the hexadecimal representation of the decimal number 42
, right-aligned to 8 bytes.
Encoding u128::MAX - 1
yields: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
, it's right-aligned to 16 bytes
Boolean
enc(X)
is 0
if X
is false or 1
if X
is true, left-padded with zero-bytes. Total length must be 8 bytes. Similar to the u8
encoding.
Example:
Encoding true
yields:
0x0000000000000001
B256
b256
is a fixed size bit array of length 256. Used for 256-bit hash digests and other 256-bit types. It is encoded as-is.
Example:
Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
yields:
0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
Address
A 256-bit (32-byte) address, encoded in the same way as a b256
argument: encoded as-is.
Example:
Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
yields:
0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
Array
An array of a certain type T
, [T; n]
, where n
is the length of the array.
Arrays in Sway have a fixed-length which is known at compile time. This means the ABI encoding for arrays also happens in-place, with no need to account for dynamic sizing.
The encoding for the array contains, in order, the encoding of each element in [T; n]
, recursively following the encoding for the type T
.
For instance, consider the function signature my_func(bool, [u64; 2])
with the values (true, [1, 2])
.
The encoding will be:
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`.
Vectors
ABI calls containing vectors are encoded in the following way:
- First, figure out the the length
l
of the vector. Its length will also be its capacity. - Encode the content of the vector according to the spec of its type, e.g. for a
Vec<bool>
, encode eachbool
element according to thebool
specs. This gives out data that is stored on the heap, and we encode only the pointer to this data
#![allow(unused)] fn main() { abi MyContract { fn foo(a: Vec<u32>); } { fn foo(a: Vec<u32>) {}; } }
Calling foo
with vec![1u32, 2u32, 3u32, 4u32]
:
length
is 4,capacity
is 4data
: [0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x0000000000000004], stored at pointer address0x000000000000beef
Note: to understand how the
u32
are encoded, see the section about encoding integers.
0x
000000000000beef // pointer address
0000000000000004 // length = 4
0000000000000004 // capacity = 4
At the pointer address, then the vector's content are encoded as such:
0x
0000000000000001 // 1u32
0000000000000002 // 2u32
0000000000000003 // 3u32
0000000000000004 // 4u32
Tuples
ABI calls containing tuples are encoded as such:
If X
is a tuple with the type signature (T_1, T_2, ..., T_n)
, with T_1,...,T_n
being any type except for vector, then enc(X)
is encoded as the concatenation of enc(T_1)
, enc(T_2)
,enc (T_3)
, ..., enc(T_n)
.
#![allow(unused)] fn main() { abi MyContract { fn foo(a: (u64, str[3], bool)); } { fn foo(a: (u64, str[4], bool)) {}; } }
Calling foo
with (1u64, "fuel", true)
:
0x
0000000000000001 // 1u64
6675656c00000000 // "fuel" encoded as per the specs
0000000000000001 // true
Version 1
This version was created to replace the older version 0 described above, and follows three philosophical tenets:
- being self-sufficient: it must be possible to completely decode the original data only using the encoded bytes and the original type (there are no references to data outside the encoded bytes);
- no overhead: only the bare minimum bytes are necessary to do the encoding. No metadata, headers, etc...;
- no relation with runtime memory layout: no padding, no alignment, etc...
Primitive Types
Primitive types will be encoded using the exact number of bits they need:
u8
: 1 byte;u16
: 2 bytes;u32
: 4 bytes;u64
: 8 bytes;u256
: 32 bytes;b256
: 32 bytes;
Arrays
Arrays are encoded without any padding or alignment, with one item after the other.
- [T; 1] is encoded [encode(T)];
- [T; 2] is encoded [encode(T), encode(T)]
Strings
String arrays are encoded just like arrays, without any overhead.
str[1]
= 1 bytestr[2]
= 2 bytesstr[n]
=n
bytes
String slices do contain their length as u64, and the string itself is encoded packed without alignment or padding.
"abc"
=[0, 0, 0, 0, 0, 0, 0, 3, "a", "b", "c"]
Slices
raw_slice
, also being dynamic, contains their length as u64 and is treated as a "slice of bytes". Each byte is encoded as u8
(1 byte) and is packed without alignment and padding.
For example, a slice of three bytes like [0u8, 1u8, 2u8]
will be encoded as bytes [0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 2]
Tuple
Tuples are encoded just like arrays, without any overhead like padding and alignment:
(A, B, C)
=[encode(A), encode(B), encode(C)]
Structs (v1)
Structs can be encoded in two ways:
- first, with the automatic implementation;
- second, with the custom implementation.
Auto implementation follows the same rules as tuples. So we can imagine that
struct S {
a: A,
b: B,
c: C
}
is encoded the same way as the tuple (A, B, C)
.
Custom implementation allows the developer to choose how a struct is encoded.
A struct has auto-implemented encoding if no custom was found.
Enums
Enums
can also be encoded with the automatic or the custom implementation.
The auto implementation first encoded the variant with a u64
number starting from zero as the first variant and increments this value for each variant, following declaration order.
enum E {
VARIANT_A: A, // <- variant 0
VARIANT_B: B, // <- variant 1
VARIANT_C: C // <- variant 2
}
will be encoded as [encode(variant), encode(value)]
.
The variant data will be encoded right after the variant tag, without any alignment or padding.
An enum has auto-implemented encoding if no custom was found.
Data Structures
Some common data structures also have well-defined encoding:
Vec
will be encoded as[encode(length), <encode each item>]
Bytes
will be encoded as[encode(length), <bytes>]
String
will be encoded as[encode (length), <data>]
All of them first contain the length and then their data right after, without any padding or alignment.
Fuel VM Specification
- Introduction
- Parameters
- Semantics
- Flags
- Instruction Set
- VM Initialization
- Contexts
- Predicate Estimation
- 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 | uint64 | 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 | instructions 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. - Base asset ID (
byte[32]
, word-aligned) 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 + 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 toMAX_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 MAX_GAS_PER_PREDICATE - $ggas
.
Predicate Verification
For any input of type InputType.Coin
or InputType.Message
, a non-zero predicateLength
field means the UTXO being spent is a P2SH
rather than a P2PKH
output.
For each such input in the transaction, the VM is initialized, then:
$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.scriptGasLimit
.
Following initialization, execution begins.
For each instruction, its gas cost gc
is first computed. If gc > $cgas
, deduct $cgas
from $ggas
and $cgas
(i.e. spend all of $cgas
and no more), then revert immediately without actually executing the instruction. Otherwise, deduct gc
from $ggas
and $cgas
.
After the script has been executed, tx.receiptsRoot
is updated to contain the Merkle root of the receipts, as described in the TransactionScript
spec.
Call Frames
Cross-contract calls push a call frame onto the stack, similar to a stack frame used in regular languages for function calls (which may be used by a high-level language that targets the FuelVM). The distinction is as follows:
- 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 | uint64 | codesize | Code size in bytes. |
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. |
Access rights
Only memory that has been allocated is accessible. Attempting to read or write memory that has not been allocated will result in VM panic. Similarly reads or writes that cross from the stack to the heap will panic. Note stack remains readable even after stack frame has been shrunk. However, if heap is alter expanded to cover that area, the crossing read prohibition still remains. In other word, memory between highest-ever $sp
value and current $hp
is inaccessible.
Ownership
Whenever memory is written to (i.e. with SB
or SW
), or write access is granted (i.e. with CALL
), ownership must be checked.
If the context is external, the owned memory range is:
[$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.
Executablity
Memory is only executable in range [$is, $ssp)
. Attempting to execute instructions outside these boundaries will panic. This area never overlaps with writable registers, essentially providing W^X protection.
FuelVM Instruction Set
- Reading Guide
- Arithmetic/Logic (ALU) Instructions
ADD
: AddADDI
: Add immediateAND
: ANDANDI
: AND immediateDIV
: DivideDIVI
: Divide immediateEQ
: EqualsEXP
: ExponentiateEXPI
: Exponentiate immediateGT
: Greater thanLT
: Less thanMLOG
: Math logarithmMOD
: ModulusMODI
: Modulus immediateMOVE
: MoveMOVI
: Move immediateMROO
: Math rootMUL
: MultiplyMULI
: Multiply immediateMLDV
: Fused multiply-divideNOOP
: No operationNOT
: InvertOR
: ORORI
: OR immediateSLL
: Shift left logicalSLLI
: Shift left logical immediateSRL
: Shift right logicalSRLI
: Shift right logical immediateSUB
: SubtractSUBI
: Subtract immediateSUBI
: Subtract immediateWDCM
: 128-bit integer comparisonWQCM
: 256-bit integer comparisonWDOP
: Misc 128-bit integer operationsWQOP
: Misc 256-bit integer operationsWDML
: Multiply 128-bit integersWQML
: Multiply 256-bit integersWDDV
: 128-bit integer divisionWQDV
: 256-bit integer divisionWDMD
: 128-bit integer fused multiply-divideWQMD
: 256-bit integer fused multiply-divideWDAM
: Modular 128-bit integer additionWQAM
: Modular 256-bit integer additionWDMM
: Modular 128-bit integer multiplicationWQMM
: Modular 256-bit integer multiplicationXOR
: XORXORI
: XOR immediate
- Control Flow Instructions
JMP
: JumpJI
: Jump immediateJNE
: Jump if not equalJNEI
: Jump if not equal immediateJNZI
: Jump if not zero immediateJMPB
: Jump relative backwardsJMPF
: Jump relative forwardsJNZB
: Jump if not zero relative backwardsJNZF
: Jump if not zero relative forwardsJNEB
: Jump if not equal relative backwardsJNEF
: Jump if not equal relative forwardsRET
: Return from context
- Memory Instructions
ALOC
: Allocate memoryCFE
: Extend call frameCFEI
: Extend call frame immediateCFS
: Shrink call frameCFSI
: Shrink call frame immediateLB
: Load byteLW
: Load wordMCL
: Memory clearMCLI
: Memory clear immediateMCP
: Memory copyMCPI
: Memory copy immediateMEQ
: Memory equalityPOPH
: Pop a set of high registers from stackPOPL
: Pop a set of low registers from stackPSHH
: Push a set of high registers to stackPSHL
: Push a set of low registers to stackSB
: Store byteSW
: Store word
- Contract Instructions
BAL
: Balance of contract IDBHEI
: Block heightBHSH
: Block hashBURN
: Burn existing coinsCALL
: Call contractCB
: Coinbase contract idCCP
: Code copyCROO
: Code Merkle rootCSIZ
: Code sizeLDC
: Load code from an external contractLOG
: Log eventLOGD
: Log data eventMINT
: Mint new coinsRETD
: Return from context with dataRVRT
: RevertSMO
: Send message to outputSCWQ
: State clear sequential 32 byte slotsSRW
: State read wordSRWQ
: State read sequential 32 byte slotsSWW
: State write wordSWWQ
: State write sequential 32 byte slotsTIME
: Timestamp at heightTR
: Transfer coins to contractTRO
: 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.
Panics
Some instructions may panic, i.e. enter an unrecoverable state. Additionally, attempting to execute an instruction not in this list causes a panic and consumes no gas. How a panic is handled depends on context:
- In a predicate context, cease VM execution and return
false
. - In other contexts, revert (described below).
On a non-predicate panic, append a receipt to the list of receipts:
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:
name | type | description |
---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 1 |
gas_used | uint64 | Gas consumed by the script. |
Receipts
The number of receipts is limited to 216, with the last two reserved to panic and script result receipts. Trying to add any other receipts after 216-2 will panic.
Effects
A few instructions are annotated with the effects they produce, the table below explains each effect:
effect 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) $rCth 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:
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:
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 | Newly allocated memory is zeroed. |
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. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000
pushes the register 61 followed by register 62.
Panic if:
$sp + popcnt(imm)*8
overflows$sp + popcnt(imm)*8 > $hp
PSHL
: Push a set of low registers to stack
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. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000
pushes the register 37 followed by register 38.
Panic if:
$sp + popcnt(imm)*8
overflows$sp + popcnt(imm)*8 > $hp
POPH
: Pop a set of high registers from stack
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. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000
pops the register 62 followed by register 61.
Note that the order is reverse from PSHH
, so that PSHH a; POPH a
returns to the original state.
Panic if:
$sp - popcnt(imm)*8
overflows$sp - popcnt(imm)*8 < $ssp
POPL
: Pop a set of low registers from stack
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. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000
pops the register 38 followed by register 37.
Note that the order is reverse from PSHL
, so that PSHL a; POPL a
returns to the original state.
Panic if:
$sp - popcnt(imm)*8
overflows$sp - popcnt(imm)*8 < $ssp
SB
: Store byte
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:
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:
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 contract id
Description | Get the coinbase contract id 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:
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:
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:
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:
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:
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:
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:
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 samebalanceRoot
andstateRoot
as on initialization. - All
OutputVariable
outputs will haveto
,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:
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 | uint64 | 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 the requested slot is unset (default) and true
if it's 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 are 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 | Additional gas is charged when a new storage slot is created. |
Panic if:
$rA + 32
overflows$rA + 32 > VM_MAX_RAM
$rB
is a reserved register$fp == 0
(in the script context)
The last 24 bytes of STATE[MEM[$rA, 32]]
are set to 0
. Register $rB
will be set to the number of new slots written, i.e. 1
if the slot was previously unset, and 0
if it already contained a value.
SWWQ
: State write sequential 32 byte slots
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 | Additional gas is charged when for each new storage slot created. |
Panic if:
$rA + 32
overflows$rB
is a reserved register$rC + 32 * $rD
overflows$rA + 32 > VM_MAX_RAM
$rC + 32 * $rD > VM_MAX_RAM
$fp == 0
(in the script context)
Register $rB
will be set to the number of storage slots that were previously unset, and were set by this operation.
TIME
: Timestamp at height
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:
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:
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.
ECAL
: Call external function
Description | Call an external function that has full access to the VM state. |
Operation | external(&mut vm, $rA, $rB, $rC, $rD) |
Syntax | ecal $rA $rB $rC $rD |
Encoding | 0x00 rA rB rC rD |
Notes | Does nothing by default, but the VM user can define this to do anything. |
This function provides an escape hatch from the VM, similar to ecall
instruction of RISC-V. The suggested convention is to use $rA
for "system call number", i.e. identifying the procedure to call, but all arguments can be used freely. The operation can modify the VM state freely, including writing to registers and memory. Again, the suggested convention is to use $rA
for the return value and $err
for any possible errors. However, these conventions can be ignored when necessary.
Panic if:
- The external function panics.
FLAG
: Set flags
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 |
GM_TX_START | 0x00005 | Transaction start memory address |
GM_BASE_ASSET_ID | 0x00006 | Base asset ID |
If imm == GM_IS_CALLER_EXTERNAL
:
Panic if:
$fp == 0
(in an external context)
Set $rA
to true
if parent is an external context, false
otherwise.
If imm == GM_GET_CALLER
:
Panic if:
$fp == 0
(in an external context)$fp->$fp == 0
(if parent context is external)
Set $rA
to $fp->$fp
(i.e. $rA
will point to the previous call frame's contract ID).
If imm == GM_GET_VERIFYING_PREDICATE
:
Panic if:
- not in a predicate context
Set $rA
to the index of the currently-verifying predicate.
GTF
: Get transaction fields
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_LIMIT | 0x002 | tx.scriptGasLimit |
GTF_SCRIPT_SCRIPT_LENGTH | 0x003 | tx.scriptLength |
GTF_SCRIPT_SCRIPT_DATA_LENGTH | 0x004 | tx.scriptDataLength |
GTF_SCRIPT_INPUTS_COUNT | 0x005 | tx.inputsCount |
GTF_SCRIPT_OUTPUTS_COUNT | 0x006 | tx.outputsCount |
GTF_SCRIPT_WITNESSES_COUNT | 0x007 | tx.witnessesCount |
GTF_SCRIPT_SCRIPT | 0x009 | Memory address of tx.script |
GTF_SCRIPT_SCRIPT_DATA | 0x00A | Memory address of tx.scriptData |
GTF_SCRIPT_INPUT_AT_INDEX | 0x00B | Memory address of tx.inputs[$rB] |
GTF_SCRIPT_OUTPUT_AT_INDEX | 0x00C | Memory address of t.outputs[$rB] |
GTF_SCRIPT_WITNESS_AT_INDEX | 0x00D | Memory address of tx.witnesses[$rB] |
GTF_TX_LENGTH | 0x00E | Length of raw transaction types in memory |
GTF_CREATE_BYTECODE_WITNESS_INDEX | 0x101 | tx.bytecodeWitnessIndex |
GTF_CREATE_STORAGE_SLOTS_COUNT | 0x102 | tx.storageSlotsCount |
GTF_CREATE_INPUTS_COUNT | 0x103 | tx.inputsCount |
GTF_CREATE_OUTPUTS_COUNT | 0x104 | tx.outputsCount |
GTF_CREATE_WITNESSES_COUNT | 0x105 | tx.witnessesCount |
GTF_CREATE_SALT | 0x106 | Memory address of tx.salt |
GTF_CREATE_STORAGE_SLOT_AT_INDEX | 0x107 | Memory address of tx.storageSlots[$rB] |
GTF_CREATE_INPUT_AT_INDEX | 0x108 | Memory address of tx.inputs[$rB] |
GTF_CREATE_OUTPUT_AT_INDEX | 0x109 | Memory address of t.outputs[$rB] |
GTF_CREATE_WITNESS_AT_INDEX | 0x10A | Memory address of tx.witnesses[$rB] |
GTF_INPUT_TYPE | 0x200 | tx.inputs[$rB].type |
GTF_INPUT_COIN_TX_ID | 0x201 | Memory address of tx.inputs[$rB].txID |
GTF_INPUT_COIN_OUTPUT_INDEX | 0x202 | tx.inputs[$rB].outputIndex |
GTF_INPUT_COIN_OWNER | 0x203 | Memory address of tx.inputs[$rB].owner |
GTF_INPUT_COIN_AMOUNT | 0x204 | tx.inputs[$rB].amount |
GTF_INPUT_COIN_ASSET_ID | 0x205 | Memory address of tx.inputs[$rB].asset_id |
GTF_INPUT_COIN_WITNESS_INDEX | 0x207 | tx.inputs[$rB].witnessIndex |
GTF_INPUT_COIN_PREDICATE_LENGTH | 0x209 | tx.inputs[$rB].predicateLength |
GTF_INPUT_COIN_PREDICATE_DATA_LENGTH | 0x20A | tx.inputs[$rB].predicateDataLength |
GTF_INPUT_COIN_PREDICATE | 0x20B | Memory address of tx.inputs[$rB].predicate |
GTF_INPUT_COIN_PREDICATE_DATA | 0x20C | Memory address of tx.inputs[$rB].predicateData |
GTF_INPUT_COIN_PREDICATE_GAS_USED | 0x20D | tx.inputs[$rB].predicateGasUsed |
GTF_INPUT_CONTRACT_CONTRACT_ID | 0x225 | Memory address of tx.inputs[$rB].contractID |
GTF_INPUT_MESSAGE_SENDER | 0x240 | Memory address of tx.inputs[$rB].sender |
GTF_INPUT_MESSAGE_RECIPIENT | 0x241 | Memory address of tx.inputs[$rB].recipient |
GTF_INPUT_MESSAGE_AMOUNT | 0x242 | tx.inputs[$rB].amount |
GTF_INPUT_MESSAGE_NONCE | 0x243 | Memory address of tx.inputs[$rB].nonce |
GTF_INPUT_MESSAGE_WITNESS_INDEX | 0x244 | tx.inputs[$rB].witnessIndex |
GTF_INPUT_MESSAGE_DATA_LENGTH | 0x245 | tx.inputs[$rB].dataLength |
GTF_INPUT_MESSAGE_PREDICATE_LENGTH | 0x246 | tx.inputs[$rB].predicateLength |
GTF_INPUT_MESSAGE_PREDICATE_DATA_LENGTH | 0x247 | tx.inputs[$rB].predicateDataLength |
GTF_INPUT_MESSAGE_DATA | 0x248 | Memory address of tx.inputs[$rB].data |
GTF_INPUT_MESSAGE_PREDICATE | 0x249 | Memory address of tx.inputs[$rB].predicate |
GTF_INPUT_MESSAGE_PREDICATE_DATA | 0x24A | Memory address of tx.inputs[$rB].predicateData |
GTF_INPUT_MESSAGE_PREDICATE_GAS_USED | 0x24B | tx.inputs[$rB].predicateGasUsed |
GTF_OUTPUT_TYPE | 0x300 | tx.outputs[$rB].type |
GTF_OUTPUT_COIN_TO | 0x301 | Memory address of tx.outputs[$rB].to |
GTF_OUTPUT_COIN_AMOUNT | 0x302 | tx.outputs[$rB].amount |
GTF_OUTPUT_COIN_ASSET_ID | 0x303 | Memory address of tx.outputs[$rB].asset_id |
GTF_OUTPUT_CONTRACT_INPUT_INDEX | 0x304 | tx.outputs[$rB].inputIndex |
GTF_OUTPUT_CONTRACT_BALANCE_ROOT | 0x305 | Memory address of tx.outputs[$rB].balanceRoot |
GTF_OUTPUT_CONTRACT_STATE_ROOT | 0x306 | Memory address of tx.outputs[$rB].stateRoot |
GTF_OUTPUT_CONTRACT_CREATED_CONTRACT_ID | 0x307 | Memory address of tx.outputs[$rB].contractID |
GTF_OUTPUT_CONTRACT_CREATED_STATE_ROOT | 0x308 | Memory address of tx.outputs[$rB].stateRoot |
GTF_WITNESS_DATA_LENGTH | 0x400 | tx.witnesses[$rB].dataLength |
GTF_WITNESS_DATA | 0x401 | Memory address of tx.witnesses[$rB].data |
GTF_POLICY_TYPES | 0x500 | tx.policies.policyTypes |
GTF_POLICY_TIP | 0x501 | tx.policies[0x00].tip |
GTF_POLICY_WITNESS_LIMIT | 0x502 | tx.policies[count_ones(0b11 & tx.policyTypes) - 1].witnessLimit |
GTF_POLICY_MATURITY | 0x503 | tx.policies[count_ones(0b111 & tx.policyTypes) - 1].maturity |
GTF_POLICY_MAX_FEE | 0x504 | tx.policies[count_ones(0b1111 & tx.policyTypes) - 1].maxFee |
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
) - The requested policy type is not set for this transaction.
For fixed-length fields, the value of $rB
is ignored.
Networks
Specifications for network-specific components of the protocol.
PoA Network
Consensus Header
Wraps the application header.
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