index Documentation

Official TypeScript developer resources and documentation for FuelLabs/index. Verified technical context for AI.

0 stars TypeScript TypeScript 1 file · ~42,074 tokens
RAW Doc

1. Project Overview & Quickstart (FuelLabs/index)

Technical Documentation: FuelLabs/index

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

index

Agent Skills & Rules

  • Discovered 21 agent rule(s) and prompt configurations.

--- METRICS ---

2. Official Technical Reference & Guides (FuelLabs/docs)

File: README.md

Fuel Docs

A complete set of documentation for the Fuel-powered tools and services.

Building From Source

Install Dependencies

bash
npm install

Build

bash
npm run build

Start

bash
npm run start

File: package.json

{
"name": "fuel-docs",
"version": "1.0.0",
"description": "The Fuel documentation.",
"main": "config/build.js",
"scripts": {
"start": "npm run build && http-server dist --cors",
"build": "parcel build public/index.html --out-dir build && node config/build",
"publish": "npm run build && now dist && now alias https://dist.fuellabs.now.sh docs.fuel.sh",
"publish-staging": "npm run build && now dist && now alias https://dist.fuellabs.now.sh docs.staging.fuel.sh",
"test": "echo "Error: no test specified" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/FuelLabs/docs.git"
},
"keywords": [
"fuel",
"docs",
"documentation"
],
"author": "Nick Dodson [email protected]",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/FuelLabs/docs/issues"
},
"homepage": "https://github.com/FuelLabs/docs#readme",
"dependencies": {
"copy-text-to-clipboard": "^2.2.0",
"fs-extra": "^9.0.1",
"markdown-to-txt": "^1.0.1",
"hyperapp": "^2.0.4",
"esm-import-regex": "1.0.0",
"hyperapp-styled-components": "^0.1.2",
"showdown": "^1.9.1",
"regenerator-runtime": "^0.13.5"
},
"devDependencies": {
"cssnano": "^4.1.10",
"http-server": "^0.12.3",
"parcel": "^1.12.4"
}
}


File: src/v1.0.0/1. Introduction/1. Welcome.md

Welcome

Before we begin:

Try out our live interactive 🕹️ Fuel Plays Pokémon demo!

Check out the ⚡ Block Explorer.

Use the one-click 💻 Wallet SDK Example.

We are always available to...

Introduction

Fuel is a permissionless and trustless layer-2 protocol for Ethereum, targeted at low-cost high-throughput value-transfer transactions. Fuel is powered by a highly-optimized optimistic rollup design.

Fuel's Features

  • Completely decentralized: non-custodial, censorship resistant, and permissionless
  • The most scalable optimistic rollup in the world, with unique UTXO-based design
  • 500 TPS maximum on-chain throughput (1,500-2,000 with BLS aggregate signatures)
  • Up to 10,000 TPS surge intake capacity
  • Send and receive using any Ethereum address, no registration required
  • ETH and all ERC-20 tokens are supported
  • Non-custodial token swaps between any tokens
  • Fees can be paid in any token, not just ETH
  • HTLCs for cross-chain atomic swaps and additional usecases
  • Withdrawals in under 10 minutes using atomic swaps

What Makes Fuel Different

  • UTXO-based design optimized for maximum scalability
    • Parallelism: transactions can be validated in parallel on consumer-grade hardware
    • No state bottleneck: unique fraud proof scheme doesn't require state serialization
  • No special key formats or on-chain account registration required
  • No upfront collateral requirements (unlike channels)
  • Permissionless and censorship-resistant (unlike Plasma/Validium)
  • No burdensome proof generation with high latency (unlike ZK rollups and Validium)
  • No exotic cryptography (unlike ZK rollups and Validium)
  • No trusted setup (unlike SNARK-based ZK rollups and Validium)
  • No worse-than-useless protocol-specific token (unlike...too many to count)
  • Care and attention to user-level issues
    • Can deposit using simple transfers from any account (including exchange accounts) using Funnel contracts
    • Can use a smart contract wallet to authorize transactions on Fuel

System benchmarks can be found on the next page.

If you want to know more about the fundamentals of Fuel's architecture, our Technical Fundamentals is your next stop. A detailed analysis on the security and scalability of Fuel is available.

If you're a developer and want to build on top of, or integrate with, Fuel, check out our Getting Started guide and wallet SDK documentation.

For examples of tools and applications that can built on top of Fuel, start here.

Finally, our technical roadmap can be found here.


File: src/v1.0.0/1. Introduction/2. Benchmarks.md

Benchmarks

Simulations

Below are simulation results for Fuel v1, for a number of different representative usecases. Usecase-specific and context-sensitive optimizations are not done, so these numbers are representative of normal usage. Such optimizations include batching multiple transactions from the same sender in the same rollup block to save space, or reducing the number of bits to represent token types to only handle a tiny number of tokens.

Running Locally

The simulation can be ran locally:

bash
git clone [email protected]:FuelLabs/fuel.git
npm install
npm run benchmark

100k Transfers

Standard one-input two-output transfers.

  • Transactions Submitted: 100000
  • Roots committed: 360
  • Blocks committed: 3
  • Cumulative gas used: 185387356
  • Ethereum blocks used: 23
  • @100 Gwei: 18.539 ETH
  • @$300 USD / ETH: $5562

100k Token Minting

One-input eight-output mass token minting transactions.

  • Claims Processed: 100000
  • Transactions Submitted: 12500
  • Roots committed: 134
  • Blocks committed: 2
  • Cumulative gas used: 72457791
  • Ethereum blocks used: 9
  • @100 Gwei: 7.246 ETH
  • @$300 USD / ETH: $2174

75k Token Burns

One-input three-output (burn, memo, change).

  • Transactions Submitted: 75000
  • Roots committed: 275
  • Blocks committed: 3
  • Cumulative gas used: 140909433
  • Ethereum blocks used: 17.613679125
  • @100 Gwei: 14.091 ETH
  • @$300 USD / ETH: $4227

25k Subscriptions

One-input two-output.

  • Transactions Submitted: 25000
  • Roots committed: 87
  • Blocks committed: 1
  • Cumulative gas used: 45442233
  • Ethereum blocks used: 5
  • @100 Gwei: 4.544 ETH
  • @$300 USD / ETH: $1363

Benchmarks

Benchmarks use the same transaction types as the simulations above (i.e. they are representative of normal usage).

Fuel v1 on Ropsten

100k Transfers

Contract deployed at 0x57f51C778Dd66153A44881Cc5E6e9da421c4dC85.

  • Cumulative gas used: 225360000
  • @100 Gwei: 22.536 ETH
  • @$300 USD / ETH: $6761

100k Token Minting

Contract deployed at 0x47b13755d14BCEFC49C0606Aca5E62Dc2C1Da9eC.

  • Cumulative gas used: 89184000
  • @100 Gwei: 8.918 ETH
  • @$300 USD / ETH: $2675

75k Token Burns

Contract deployed at 0xf73788cD87267c68dA5ed3e3a37c4A610a07513d.

  • Cumulative gas used: 176400000
  • @100 Gwei: 17.640 ETH
  • @$300 USD / ETH: $5292

25k Subscriptions

Contract deployed at 0x232113aaC91Bab5b6351016497f966dA8C1BfB9C.

  • Cumulative gas used: 56112000
  • @100 Gwei: 5.611 ETH
  • @$300 USD / ETH: $1683

Audits


File: src/v1.0.0/1. Introduction/3. Use Cases.md

Use Cases

In this section we explore various use cases for the Fuel v1 optimistic rollup. All the use cases covered below are examined in more detail in the Tools and Applications section.

Transfers

Fuel can be used for high-performance, low-latency global transfers with any ERC-20 token or Ether.

This is ideal for:

  • Global payment settlement
  • Application-specific tokens
  • Virtual gaming tokens and rewards
  • Any situations where users are transferring ERC-20 tokens

Please see our interactive 🕹️ Fuel Plays Pokémon demo for a live example of transfers in action.

Non-Custodial Exchange

Fuel's unique multi-user transactions and HTLC support are a perfect foundation for non-custodial exchanges.

We support two kinds of exchange:

  • Multi-user on-Fuel swaps
  • Cross-chain atomic swap with HTLCs

For further reading, please see Non-Custodial Exchanges.

Mass Token Dispersal

Fuel supports multi-input, multi-output transactions, making it perfect for pre-signed mass token dispersal scenarios where a single account must disperse tokens to a large number of unique recipients cost-efficiently.

This is possible on Fuel with a Merkleized dispersal pattern and our deterministic transaction identifiers, allowing dispersal transactions to be pre-signed and distributed off-chain. Tokens are only actually dispersed when claimed, greatly reducing overhead costs for unclaimed tokens.

For further reading on how we accomplish this with low overheads, please see Token Minting

Fast Withdrawals

Fuel allows for fast (~10 minute) withdrawals using our unique HTLC output. Liquidity providers on the intended exit chain (e.g. Ethereum or Bitcoin) can allow users on Fuel to withdraw with minimal effort and at low cost.

For further reading, please see Fast Withdrawals.

Subscriptions

Fuel's pre-signed transaction format is ideal for creating regular financial subscriptions, where a user can sign up to a subscription, pre-signing a set of transactions, and then allowing any service or system to collect payment on a regular basis non-interactively.

This is allows for cheap, care-free subscriptions for magazines, blogs, forums, and more.

For further reading, please see Subscriptions.

Burning

Fuel has a "return data" transaction output, which can be used to store arbitrary data in Fuel such as: burn metadata, social media messages, timestamped hashes, or specific application data.

The burning of a token by sending it to the zero address can be associated with a specific event using the return data output, all within in the same transaction.


File: src/v1.0.0/2. Getting Started/0. Using the Wallet SDK.md

Using the Wallet SDK

The wallet is how users and developers can interact with Fuel.

Install

The Fuel module is currently available as a UMD through CDN or NPM.

node.js

bash
npm install --save @fuel-js/wallet

browser (UMD)

html
<script src="https://unpkg.com/@fuel-js/wallet@latest/umd/fuel.umd.min.js" type="text/javascript">
</script>

browser (ESM)

html
<script type="module">
  import fuel from "https://unpkg.com/@fuel-js/wallet@latest/umd/fuel.umd.min.js";
</script>

Creating a Wallet

The Fuel wallet can be used with or without a standard web3 or ethers provider. The provider will be used for account signing or any Ethereum related actions, such as deposits and withdrawals.

If an override privateKey is not specified, the first account given by the provider will be used.

If an Ethereum network is not specified (e.g. rinkeby), it will be requested from the provider.

js
import fuel from '@fueljs/wallet';

// no provider, privateKey is generated
const wallet = fuel.wallet(null);

// web3 provider, first account used, network gathered from provider
const wallet = fuel.wallet(window.web3.currentProvider);

// provider with override privateKey and network specified
const wallet = fuel.wallet(window.web3.currentProvider, {
  privateKey: fuel.utils.hexlify(fuel.utils.randomBytes(32)),
  network: 'rinkeby',
});

Fauceting

All Fuel test networks have a token faucet that will dispense fake tokens to requesting accounts (within several seconds to minutes).

The faucet will dispense 1000 faucetTokens.

The faucet test token identifier will always be available in fuel.contants.faucetToken.

js
import fuel from '@fueljs/wallet';

// new wallet
const wallet = fuel.wallet(null, {
  network: 'rinkeby',
});

// faucet fake tokens
await wallet.faucet();

// check your balance
console.log('my balance: ', await wallet.balance(fuel.constants.faucetToken));

Depositing

You can deposit ERC-20 tokens or Ether into Fuel by using the deposit method. Simply specify the token and amount to deposit. Since Fuel uses Ethereum key pairs, this process can be done directly with your provided MetaMask account, for example.

The deposit method will make two Ethereum transactions, depositing your funds into Fuel and making them accessible for Fuel activities (e.g. transfers and swaps).

js
import fuel from '@fuel-js/wallet';

const wallet = new fuel.Wallet(window.web3.currentProvider);

(async () => {

  // rinkeby dai address
  const daiAddress = '0xc3dbf84Abb494ce5199D5d4D815b10EC29529ff8';

  // deposit funds
  await wallet.deposit(daiAddress, fuel.utils.parseEther('1.0'));

})();

Transferring

Now that you have funds in Fuel, either by deposit or faucet you can begin to transfer them around.

Let's try transferring 1 Fuel token to our own wallet address and check the submission by its transaction ID.

js
import fuel from '@fuel-js/wallet';

const wallet = new fuel.Wallet(window.web3.currentProvider);

(async () => {

  await wallet.faucet();

  // transfer one faucet token to our own wallet
  const { transactionId } = await wallet.transfer(
    fuel.constants.faucetToken,
    wallet.address,
    fuel.utils.parseEther('1.0'));

  console.log('view my transaction: https://rinkeby.fuel.sh/tx/' + transactionId);

})();

Withdrawing

Currently, the Fuel wallet only supports standard withdrawals, which means you will send an initial withdraw transaction on Fuel first, than release it after the finality delay of 1 week has expired. In the future, the wallet will support fast withdrawals via atomic swaps, where total withdraw time can be reduced safely to as little as 5-10 minutes.

In order to release the tokens on Ethereum, you must have some Ether for the transaction.

js
import fuel from '@fuel-js/wallet';

const wallet = new fuel.Wallet(window.web3.currentProvider);

(async () => {

  await wallet.faucet();

  // withdraw one faucet token
  await wallet.withdraw(fuel.constants.faucetToken, utils.parseEther('1.0'));

  // wait one week...

  // release any withdrawals onto Ethereum
  await wallet.release();
})();

File: src/v1.0.0/2. Getting Started/1. One-Click Example.md

One-Click Example

Below is our one-click Wallet SDK example using the Fuel test faucet on Rinkeby.

Try it out!

await import('https://unpkg.com/@fuel-js/wallet@latest/umd/fuel.umd.min.js');

// Create a new wallet with a new key
// Tip: try setting null to web3.currentProvider to run with MetaMask
const wallet = new fuel.Wallet(null, {
network: 'rinkeby',
});

// Get yourself some fake Moons
await wallet.faucet();

// Establish faucet token id
const faucetToken = fuel.constants.faucetToken;

// Get your fake moons balance
console.log('my balance: ',
await wallet.balance(faucetToken, { format: 'ether' }));

// Send yourself some faucet token
const tx = await wallet.transfer(faucetToken, wallet.address, '4.5', { units: 'ether' });

// Checkout your transaction on the Block Explorer
console.log('check out your tx: ' + window.rinkebyTx + tx.transactionId);

run


File: src/v1.0.0/2. Getting Started/2. Running a Node.md

Running a Node

The Fuel client can be used to sync with Fuel and fully validate the chain.

Binary Releases

Binary releases can be found at https://github.com/FuelLabs/fuel-js/releases

Running from Command Line

Download a release (above) and use an available network specifier and RPC endpoint to get started.

bash
fuel --network="rinkeby" --rpc="https://localhost:8545"

Additional Parameters

bash
⚡ fuel [options]

  Options:

    -n, --network         the ethereum network "rinkeby"; default "rinkeby"
    -r, --rpc             a standard Ethereum RPC provider (i.e. local go-ethereum)
    -i, --infura          an Infura service API key (--network must also be specified)
    -es, --etherscan      an Etherscan service API key (--network must also be specified)
    -e, --environment     use the environment variables to specify node parameters
    -o, --operators       a comma-separated list or seed phrase of Ethereum private keys used for block, root, and fraud commitments

  Examples:

    $ fuel --network="ropsten" --rpc="http://localhost:8545"

Running from GUI

Coming Soon™...


File: src/v1.0.0/2. SDK/1. Wallet.md

Wallet SDK

new

new fuel . Wallet ( provider [, options ] )

Installation

node.js

bash
npm install --save @fuel-js/wallet

browser (UMD)

html
<script src="https://unpkg.com/@fuel-js/wallet@latest/umd/fuel.umd.min.js" type="text/javascript">
</script>

browser (ESM)

html
<script type="module">
  import fuel from "https://unpkg.com/@fuel-js/wallet@latest/umd/fuel.umd.min.js";
</script>

Usage

js
import fuel from '@fuel-js/wallet';

// use the window provider key / account / eth_signTypedData_v4 for transaction signing
const wallet = new fuel.Wallet(window.web3.currentProvider);


(async () => {

  // make a deposit
  await wallet.deposit(daiAddress, fuel.utils.parseEther('1.0'));

  // make a transfer
  await wallet.transfer(daiAddress, recipientAddress, fuel.utils.parseEther('1.0'));

  // listen for new inputs and balance changes
  wallet.on('input', input => {
    console.log('listening for new inputs: ', input);
  });

  // get your address
  console.log('my address', wallet.address);

})();

Description

The Wallet object allows you to interact with a Fuel instance.

This object is heavily powered by the ethers library. web3.js providers are also supported.

The Wallet wrapper will simply use the provided provider object's eth_signTypedData_v4 method for signing transactions.

If neither are available, you can use the Wallet object as a read-only wallet. You may also provide an override privateKey which will be used in place of the provider. The storage and management of this privateKey is up to the developer.

Ethereum gasPrice and gasLimit options are only needed when using the force feature of SDK methods, which will allow users the option of creating their own rollup blocks.

Params

  • provider : Web3Provider | EthersProvider; a web3 or ethers Provider object

  • options : Object

    • privateKey : Hex | Uint8Array | SignerKey; the override 32 byte Ethereum standard private key used for signing transactions. Used instead of the provider signing using eth_signTypedData_v4
    • gasPrice : Hex | BigNumber; the Ethereum gas price for forced transactions
    • gasLimit : Hex | BigNumber; the Ethereum gas limit for forced transactions
    • address : Hex; the read-only 20-byte hex string address for watching balances
    • db : LevelDown; a leveldown-compliant database instance for storing inputs and outputs temporarily or persistently
    • sync : Boolean; sync before each wallet action, this can be turned off for better performance but you will have to manage sync manually by using await wallet.sync(), see Performance and Syncing for more details.

Throws

  • If the db is invalid or will not open
  • If the privateKey is invalid or incorrect, if provided
js
const wallet = new fuel.Wallet(window.web3.currentProvider, {
  db: require('level-js')(), // a leveldown-compliant database
});

deposit

wallet . deposit ( token, amount [, options] ) => Promise<TransactionResponse>

Description

The deposit method will deposit ERC-20 tokens or ETH into the Fuel.

Params

  • token : Hex; Number; BigNumber; the 20-byte ERC-20 token address or ID number
  • amount : Number | Hex | BigNumber; the amount to deposit into Fuel
  • options : OptionsObject

Returns

This method returns a TransactionResponse object.

Throws

  • If there is insufficient balance of the ERC-20 token or ETH to cover amount and gas fees
  • If the timeout has passed and no Fuel block producer has picked up the deposit
js
await wallet.deposit(daiAddress, 500000, {
  // gasPrice: ...,
  // gasLimit: ...,
  // from: '0x...',
  // timeout: 10000, // seconds
});

faucet

wallet . faucet ( [, options] ) => Promise

Description

This method will call the Fuel network faucet, based upon your network, and send the wallet.address test tokens to play with.

Note, this method might take up to a few minutes to work and is rate-limited.

Params

Throws

  • If the options.timeout, default 100 seconds (or 100000 micro-seconds) times out.
js
await wallet.faucet();

balance

wallet . balance ( token, [, options] ) => Promise<BigNumber>

Description

This method will give you your current balance for a specific token.

Params

  • token : Hex; Number; BigNumber; the 20-byte ERC-20 token address or ID number
  • options : OptionsObject

Returns

This method returns a BigNumber object containing the balance.

  • balance : BigNumber; the balance for the token

Throws

  • If the token is invalid or is not registered with Fuel
js
await wallet.balance(daiAddress);

transfer

wallet . transfer ( token, to, amount [, options] ) => Promise<TransactionResponse>

Params

  • token : Hex; Number; BigNumber; the 20-byte ERC-20 token address or ID number
  • to : Hex; the 20-byte recipient address
  • amount : Number | Hex | BigNumber; the amount to transfer
  • options : OptionsObject

Returns

This method returns a TransactionResponse object.

Throws

  • If there is insufficient balance of the ERC-20 token or ETH to cover amount and fees
  • If there is a connection or provider issue
js
await wallet.transfer(daiAddress, someAddress, 250000, {
  // inputs: [], // specify explicit array of UTXO IDs to spend
  // feeLimit: 2000,
  // from: '0x....',
  // gasPrice: ..., // only used if forcing a tx
  // gasLimit: ..., // only used if forcing a tx
  // force: true, // will attempt for force tx on-chain
});

withdraw

wallet . withdraw ( token, amount [, options] ) => Promise<TransactionResponse>

Description

This function allows you to start a long withdraw from Fuel. You first send the withdraw transaction on Fuel, then you complete the withdrawal on Ethereum 2 weeks later.

Params

  • token : Hex; Number; BigNumber; the 20-byte ERC-20 token address or ID number
  • amount : Number | Hex | BigNumber; the amount to withdraw
  • options : OptionsObject

Returns

This method returns a TransactionResponse object.

Throws

  • If there is insufficient balance of the ERC-20 token or ETH to cover amount and fees
  • If there is a connection or provider issue
js
await wallet.withdraw(daiAddress, 250000, {
  // inputs: [], // specify explicit array of UTXO IDs to spend
  // feeLimit: 2000,
  // from: '0x....',
  // gasPrice: ..., // only used if forcing a tx
  // gasLimit: ..., // only used if forcing a tx
  // force: true, // will attempt for force tx on-chain
});

retrieve

wallet . retrieve ( [options] ) => Promise<TransactionResponse>

Description

This will complete finalized withdrawals. Withdrawals must first be started using withdraw.

Params

Returns

This method returns a TransactionResponse object.

Throws

  • If there is insufficient balance of the ERC-20 token or ETH to cover amount and gas fees
  • If there is a connection or provider issue
js
// after 2 weeks...
await wallet.retrieve({
  // inputs: [], // specify explicit array of utxo hashes to retrieve
  // from: '0x....',
  // gasPrice: ..., // only used if forcing a tx
  // gasLimit: ..., // only used if forcing a tx
});

sync

wallet . sync ( [options] ) => Promise<Void>

Description

The sync method will simply download the current UTXOs for this wallet from the Fuel provider.

Throws

  • If there is a connection or provider issue
  • If there was an issue getting the private account via eth_getAccounts
  • If there was an issue getting the current Ethereum network from the provider
js
// sync your wallet with the Fuel api
  await wallet.sync();

on

wallet . on ( type, listener )

Description

The on method is used to register listeners for incoming transactions from the Fuel API. Once an on listener is registered, the wallet becomes live, and balances will automatically update.

If there is a problem with the listener at any time or it fails to bind, the error type will be used.

Params

  • type : String; either input or error
  • listener : Function; a callback function which will include a single argument: a UTXO, Root or Deposit proof object
js
wallet.on('input', utxoOrRootProof => {
  console.log(utxoOrRootProof);
});

wallet.on('error', error => {
  console.log(error);
});

fee

wallet . fee ( token ) => Promise<BigNumber>

Description

Get the current feerate for an ERC-20 token.

Params

  • token : Hex; Number; BigNumber; the 20-byte ERC-20 token address or ID number

Returns

  • fee : BigNumber; the feerate for this ERC-20 token

Throws

  • If this token has no feerate available, i.e. the Fuel operator has not allowed this token yet
js
// get the current fee for processing a specific token
  await wallet.fee(daiAddress);

Performance and Syncing

Note, that the Wallet by default will sync before each action (i.e. transfer, balance etc), you may turn this off by setting { ..., sync: false } in either the global wallet options or per each method.

If you do this, you must manually manage sync operations using the wallet.sync() method when needed (e.g. upon account load or when the wallet balance is zero).

Objects

OptionsObject

  • inputs : Array [ Hex, ... ]; an array of 32-byte hex values specifying either the Root, Deposit or UTXO IDs you would like to spend
  • feeLimit: BigNumber; the maximum feerate to accept for submitting the transaction
  • gasPrice : Hex | BigNumber; the Ethereum gas price for forced transactions
  • gasLimit : Hex | BigNumber; the Ethereum gas limit for forced transactions
  • from : Hex; the 20-byte sender address to specify either the provider signer or the override private key
  • timeout : Number; the timeout to wait in seconds for the Promise to either fail or be accepted
  • force : Boolean; force this method into a single Root on Ethereum
  • sync : Boolean; do an account sync before the given method or action.

UTXO

  • transactionId : Hex;
  • outputIndex : BigNumber;
  • outputType : BigNumber;
  • owner : Hex;
  • amount : BigNumber;
  • token : BigNumber;
  • digest : Hex;
  • expiry : BigNumber;
  • returnOwner : Hex;

Root

  • producer : Hex;
  • merkleTreeRoot : Hex;
  • commitmentHash : Hex;
  • length : BigNumber;
  • feeToken : BigNumber;
  • fee : BigNumber;

Deposit

  • owner : Hex;
  • token : BigNumber;
  • blockNumber : BigNumber;
  • value : BigNumber;

BigNumber

  • This is an ethers wrapped BigNumber object, using BN.js under the hood.

Response Objects

TransactionResponse

  • transactionId : Hex; the Ethereum transaction hash
  • receipts : Array; an array of Ethereum transactions receipts, if any. Namely for deposits, withdrawals or forced transfers.

File: src/v1.0.0/3. Concepts/0. Fundamentals/1. Fuel Overview.md

Fuel Overview

This section of the docs goes over the fundamental technical details of the Fuel rollup chain: what it is, how it works, and why it is scalable and secure.

Background

Fuel is an optimistic rollup, a special sidechain architecture that is both trust-minimized and permissionless. Unlike a traditional sidechain, where the safety of user funds is dependent on the honest majority of some set of validators that is disjoint from the main chain's miners, optimistic rollups borrow security directly from the main chain (Ethereum).

The simplified version of optimistic rollups is as follows. Anyone may construct a rollup block off-chain and submit it to Ethereum as calldata (i.e. simple binary data attached to a transaction). Each block submitted in this manner must build upon the previous rollup block (a contract keeps track of block header hashes for the rollup chain) and include a parametrizable bond. If a block is invalid, a compact fraud proof can be submitted and processed on-chain by anyone, which will roll back the rollup chain to the previous block, burn a portion of the bond, and reward the fraud prover with the rest. After a parametrizable timeout, rollup blocks are finalized, and are considered valid (i.e. can no longer be proven fraudulent) and their bond unlocked. Withdrawals are initiated by burning coins on the rollup, then completing the withdrawal after that rollup block has finalized.

Interacting with Fuel

Fuel node interacting with Ethereum.

Users send Fuel transactions to a Fuel node, which enters the node's mempool as an unconfirmed transaction. The client combines transactions into a Fuel block, which is then sent to Ethereum as calldata, at which point the Fuel block is confirmed. The node also reads deposit events from Ethereum, adding deposits as spendable state elements on Fuel.

Fuel normal (above) and forced (below) transactions.

There are two ways of getting Fuel transactions included in blocks. In the first, under normal operation, the users send transactions to a Fuel node, which then combines them into batches (roots), and finally into a block (which contains several roots). In the second, a user wants to use their own Fuel node to submit a new block, i.e. a forced transaction. In this case, they can create a root and block independently of any other node and submit it to Ethereum directly.

Entering Fuel.

Depositing funds into Fuel is done by simply sending them to the Fuel contract (or optionally through a token funnel), at which point the deposits will be added to the state of the Fuel chain.

Exiting Fuel normally (above) with quickly (below).

Withdrawing funds from Fuel can be done in two ways: standard and fast. For standard withdrawals, a user simply burns their coins on Fuel, then after a delay, can complete the withdrawal on Ethereum. For fast withdrawals, a user contacts a liquidity provider and atomic swaps funds from Fuel to Ethereum in a matter of minutes.

Architecture: A High-Level View

Fuel is unique among Ethereum rollups in that it uses a UTXO-based transaction system. This allows for parallel transaction validation, and in the future stateless predicate validation for spending conditions, greatly increasing scalability compared to contemporary accounts-based blockchains and rollups.

Each transaction can spend up to eight inputs and produce up to eight outputs. This allows atomic multi-user transactions, e.g. for an on-chain atomic exchange, which also means that non-custodial exchanges can be built on top of Fuel. The deterministic and stateless nature of computing state element (UTXO) IDs produced and consumed by transactions enables long chains and even trees of pre-signed transactions to be constructed.

State elements (UTXOs and deposits) can be colored, i.e. represent different assets. This allows transactions to natively handle both ETH and any ERC-20 token.

Fuel supports a number of spending conditions that allow for powerful implicit expressivity:

  • HTLC outputs, which have both a hashlock and a timelock. These enable cross-chain atomic swaps (including between Fuel and Ethereum for instant withdrawals). They can also be used as a signaling mechanism (e.g. to vote in a DAO), and when combined with a chain of pre-signed transactions, for cancellable subscriptions.
  • Return outputs, which are unspendable and can include a long binary blob. These can be used to build applications that are executed client-side and use Fuel for timestamping messages cheaply.

The fraud proof scheme used by Fuel is unique in that it does not require state serialization (computing the Merkle root of the state) after every transaction, or even after every block. State serialization is one of the biggest bottlenecks for Ethereum and accounts-based rollups today, and remains a Herculean challenge to optimize implementations around. This, along with parallelizable transaction validation, make Fuel the most scalable rollup in the world.

Fuel's block production model is entirely permissionless, and requires no token (and will never require a token). Anyone may start by submitting a root, a bundle of Fuel transactions, to Ethereum. The root can then be included in a Fuel block by anyone, with a small caveat. The Fuel operator has a short period of time (on the order of minutes, a parameter) where they are the only one that can use this root in a block. This allows the operator to reliably provide a soft confirmation to a recipient that a transaction will be included in the next Fuel block. While this is the default and preferred configuration, using an operator is a system parameter that can be disabled trivially on contract creation if soft confirmations aren't needed.

For a detailed analysis of Fuel's security guarantees, and how it compares to other scaling technologies, see the next page. (Warning: this document assumes a high level of technical proficiency.)

A more in-depth survey of Fuel's components is given here, with block and transaction architecture descriptions also available.

The canonical serialization scheme is defined here.

Finally, the parameters used for deployments across different networks is found here.


File: src/v1.0.0/3. Concepts/0. Fundamentals/2. Security Analysis.md

Security Analysis

This page will provide a comprehensive overview of the security and scalability properties of Fuel as it compares to other so-called "scaling solutions." It argues that Fuel is uniquely positioned in the trade-off space to be the best scaling architecture today.

Common Definitions

We must first begin by establishing common definitions for desirable properties in a scaling solution. The following definitions are informal, but sufficiently precise to enable proper discussion on the matter. Note that they apply to both "layer-2" constructions as well as base "layer-1" blockchains.

Security Security can be roughly defined as the cost to manipulate history. Example: the security of Proof-of-Work blockchains can be measured as the capex and opex of producing new blocks. The security of Proof-of-Stake blockchains can be measured as the penalty that can be applied if a history re-organization is done.

Trustless Trustlessness has two components: state liveness and state safety. State is live if it can be consumed by its owner in finite time (in other words, it cannot be burned/locked forever), and safe if it cannot be consumed by its non-owners ever (in other words, it cannot be stolen). This is a more precise concept than non-custodial. It is critical to evaluate the trustlessness of a system across both these dimensions, not only state safety. It is also critical to consider worst-case scenarios (i.e. failure modes), not only the optimistic case.

Decentralized blockchains cannot be completely trustless; to illustrate, consider that 51% of miners in a Proof-of-Work blockchain could censor any future transaction (effectively burning censored funds, which allows for indirect theft) or even re-organize the history from genesis to steal all funds. Therefore we measure trustlessness as the assumptions under which a system is trustless. As an example, if we assume 51% of miners are honest, then a blockchain is trustless. We can describe a system that is almost as trustless as another as trust-minimized with respect to the other (e.g. a trust-minimized sidechain is trustless under almost the same assumptions as its main chain).

Permissionless A system is permissionless if it requires no in-system action for a new user to begin participating. It is permissioned otherwise.

Decentralization While commonly (and incorrectly) described as "the number of nodes," decentralization is measured as the cost to run a fully validating node. Actually running a full node at all times is not required, but we must consider the worst-case scenario of having to run a full node. While permissionlessness is a necessary property for decentralization, it does not otherwise provide a metric for measuring decentralization. Note: this definition is actually missing a very important detail, but will suffice for now.

Scalability and Throughput Often a point of contention and confusion, scalability is front-and-center as the most obvious problem for contemporary blockchains. First, throughput is the total transaction throughput of the system, measured in transactions per second. This is often the metric incorrectly advertized as scalability. Scalability is the ratio of total transaction throughput to cost of running a node. Therefore, comparing transactions per second between two systems without simultaneously comparing their costs is comparing apples to oranges. It is trivial to increase throughput by just increasing the cost to run a node, but that is not an increase in scalability.

Note: It is critical to consider worst-case scalability, not average-case or optimistic-case. Blockchains are adversarial environments; if something can be griefed and DoSed it will be griefed and DoSed.

Note: While the naming of the above properties may certainly be debated, that they are desirable properties in any scaling solution, and good metrics for evaluating such solutions, cannot. Trustlessness is especially critical; consider needing to choose between using an unscalable decentralized blockchain and a high-throughput custodial exchange. Neither option is even a choice.

Desiderata

In order to be a "good" layer-2 scaling solution, we need the following properties simultaneously:

  1. The system must have equal or almost-equal security to the main chain. Non-trivial trade-offs in security are unacceptable from a scaling solution.
  2. The system must be trust-minimized with respect to Ethereum. Adding meaningful trust assumptions, similar to reducing security, is unacceptable.
  3. The system should be permissionless. This is not a hard requirement, so long as the system is trustless, but is highly desirable.
  4. The system must have higher throughput than Ethereum.
  5. The system should be more scalable than Ethereum. In the context of decentralization (cost of running a full node): the marginal cost of each layer-2 transaction must be lower than the marginal cost the same transaction at layer-1.
  6. Additional desiderata include:
    • No need for hot keys
    • Backups support, only private keys are sufficient to recover funds
    • No novel cryptography
    • Capital efficient
    • Low latency
    • Generalizability/expressivity

Lastly, similar decentralization to Ethereum: running a node for the layer-2 must not be more expensive than running a full node for Ethereum. Note that this is a deployment parameter, and is not intrinsic to any of the schemes discussed here. To have a common basis for comparison: we want to be able to handle more transactions in our layer-2 than layer-1 Ethereum (with the same or similar trust assumptions), with the same hardware.

Put another way, everyone who could ever be interested in a particular interaction must fully validate that interaction (trust-minimization). If a layer-2 interaction is cheaper to validate than it would have been to validate on layer-1, then the layer-2 system provides scalability. If there are trustless guarantees that a particular interaction does not need to be validated by all users, then this can increase total transaction throughput without proportionally increasing full node costs (i.e. unconditional scalability). If there are no guarantees of this, then there may be conditional scalability, usually by deploying multiple instances of a scaling scheme with different participants.

Channels

Channels are one of the earliest blockchain scaling proposals. Originally taking the form of payment channels, they can be extended to arbitrary computation with state channels.

A fixed set of participants open a channel with an on-chain transaction that defines the rules of the channel. Updates to the channel state are made by unanimous consent: all participants signing a state update, communicated off-chain. Any participant may attempt to close the channel on a particular state, which starts a timeout during which anyone may submit a later state to penalize a fraudulent channel close attempt. If no later state has been shown by the timeout, the channel closes, settling balances based on the closing state.

Metrics:

  1. Same security as the main chain. Channel updates are not timestamped to Ethereum, so technically it is possible to manipulate the channel history, but only under the same failure mode as a trust-minimization failure (below).
  2. Trust-minimized, with an additional trust assumption: if the majority of miners censor transactions (which requires attacking the main chain), a proof of fraudulent channel close attempt can be censored, allowing theft of funds. Miners can already indirectly steal funds through censorship of the main chain, so this is not a meaningfully stronger trust assumption.
  3. It is permissionless to open a channel, but participation in any specific channel is permissioned and fixed. This means e.g. Uniswap cannot be implemented using only channels.
  4. The total aggregate throughput can be substantially increased by using channels.
  5. For a single channel, scalability depends on the execution model used, but it will usually be much cheaper to execute a transaction in a channel than it would be to validate it on Ethereum. For multiple channels, there is conditional scalability if the sets of participants is disjoint, which is usually the case since open-participation systems cannot be built with channels in the first place.
  6. Additional concerns:
    • Hot keys are required to take advantage of the low latency of channels.
    • Backups are not possible, only copies (backups can recover everything but a delta, while copies are all-or-nothing). Only the latest channel state is useful, and in fact old channel states are worse-than-useless as attempting to close the channel with an old state will result in a penalty. Private key backups are insufficient to ensure funds cannot be lost.
    • Capital inefficient: funds must be locked up inside a channel before being used, and cannot be unlocked quickly without cooperation from the specific channel participants.
    • Instant finality: as soon as a state update is signed unanimously, it is instantly finalized.
    • Generalizable to contracts between a fixed participant set.

In summary, while trust-minimized, channels make enormous sacrifices in terms of capital efficiency, user experience, and open participation to achieve instant finality, which is a feature not possible with blockchains. As such, unless instant finality is a critical requirement, channels are unsuitable as a scaling solution.

Channel Networks

Channel networks link together multiple channels with atomic multi-channel interactions. This allows for interactions between distinct channels, but still does not allow open participation on a shared state (i.e. Uniswap cannot be implemented using a channel network). They are unreliable unless an overwhelming majority of channel network participants are online simultaneously, which facilitates and aggravates a number of attacks.

In summary, channel networks do not diverge meaningfully from channels in the trade-off space.

Sidechains

Sidechains are blockchains that fully validate another blockchain as part of their consensus rules (e.g. a sidechain to Ethereum is a blockchain that validates the Ethereum chain). There are a number of ways sidechain blocks can be produced, including Proof-of-Authority (PoA, a single block producer), a federation, Nakomoto Consensus with Proof-of-Work (PoW), or even a consensus protocol using Proof-of-Stake (PoS) for Sybil resistance.

The two-way peg design of the sidechain (i.e. the mechanism for locking tokens for use on the sidechain, then unlocking them back to the main chain) is critical. A trust-minimized two-way peg is ideal, but is not feasible with plain sidechains.

Metrics:

  1. Sidechains can have the same security as the main chain by timestamping sidechain block header hashes on the main chain.
  2. A trust-minimized two-way peg is not possible with plain sidechains, since data withholding is not a uniquely attributable fault. A sidechain that adds features to make its two-way peg trust-minimized ends up becoming a Plasma or a rollup.
  3. Due to data withholding, sidechains degenerate to being permissioned: a cabal of sidechain block producers can create a block that other sidechain block producers can never build upon.
  4. Any number of sidechains may be deployed in parallel, so throughput can be greatly increased.
  5. Sidechains may have any execution model. As such, they can have a different—and more scalable—execution model than the main chain.
  6. Additional concerns:
    • Sidechains are flexible: they can be faster, cheaper, and more expressive than the main chain.

In summary, plain sidechains have a number of nice features, namely their flexibility, but are not trust-minimized. As such, they are unacceptable as a scaling solution.

Plasma

Plasma is a scheme for trust-minimized but permissioned sidechains with exponentially lower on-chain costs. This requires transaction data to be kept entirely off-chain. Different flavors of Plasma (described below) have some common elements. Each Plasma block header is submitted by an operator to a contract on Ethereum (which means Plasma has at least as high a latency as Ethereum). Withdrawals from the Plasma chain (i.e. exits) are involved, and are what allows for (in theory) a trust-minimized two-way peg.

All Plasma variants are permissioned, or degenerate to become permissioned. Even if more than a single operator is used (e.g. using some PoS protocol for Plasma block production) a cabal of operators can produce a block and withhold data, preventing other operators from ever building upon it. In fact, using PoS for Plasma block production is actively detrimental and worse-than-useless, as it adds friction and cost to end users without actually providing any of the nice properties of PoS-based consensus protocols. This is because we cannot recover from PoS consensus failures without hard forks, and it is impossible to trustlessly upgrade a smart contract.

This scaling scheme has mostly been abandoned in favor of rollups for additional reasons that will become apparent soon.

Plasma MVP

Plasma MVP variants were the first concrete flavor of Plasma. A Plasma operator aggregates transactions off-chain, then commits to them in a Plasma block header that is processed on-chain. The UTXO data model is used, but the accounts data model can be used just as well. Exits are initiated on Ethereum against any block. If an exit is invalid (e.g. trying to exit a spent coin), a fraud proof can cancel it; otherwise, the exit is placed in a priority queue based on Plasma block number. After a timeout, the exit can be completed and funds withdrawn from the contract.

However, since Plasma block data can be withheld, the operator can create an invalid Plasma block that gives them all funds, then exit them. Block data is unavailable, so no one can prove the exit is fraudulent. Therefore all users must exit before the timeout expires by exiting their funds from an earlier (available) Plasma block than the malicious operator's exit. This is called a mass exit, an involves putting the Plasma chain's entire state onto Ethereum in a short amount of time.

Metrics:

  1. Security is borrowed from the main chain, as Plasma blocks are timestamped by Ethereum.
  2. Plasma MVP variants are only trust-minimized in theory. In practice, mass exits allow an adversary to start an attack with constant cost (posting a single invalid Plasma block header on-chain) that requires unbounded cost to defend against (users must exit the entire state in a short time, and state is essentially unbounded in size). Therefore Plasma MVP variants degenerate in practice to sidechains.
  3. Plasma MVP is a Plasma and thus permissioned.
  4. Plasma MVP may have higher throughput depending on the execution model. However, this is purely dependent on the execution model (and has no relation to having a single operator), as all users must fully validate the Plasma MVP chain to ensure their funds are safe.
  5. Increased scalability is possible with a more efficient execution model than the EVM.
  6. Additional concerns:
    • No need for hot keys.
    • Since data is not available on-chain, users must save a copy of their off-chain state. Private key backups are insufficient to ensure funds cannot be lost.
    • Capital efficient for both users and the operator.
    • Latency cannot be lower than Ethereum, as Plasma blocks are only valid once they are committed to Ethereum.
    • Ethereum-style smart contracts are not possible on Plasma. Predicate spending conditions on UTXOs are possible.

In summary, Plasma MVP variants degenerate to sidechains, with funds being trivial to steal by the operator. As such, they are unacceptable as a scaling solution.

Plasma Cash

Plasma Cash variants are the second concrete flavor of Plasma. As with Plasma MVP, a Plasma operator aggregates transactions off-chain, then commits to them in a Plasma block header that is processed on-chain. However, unlike Plasma MVP, a coins data model is used, where each state element is a non-fungible coin (similar to a channel, each coin is uniquely identifiable and cannot be broken up or combined). In order to accept a coin, its entire transaction history must also be downloaded and validated. Exits are initiated on Ethereum against any block. An invalid exit can be disputed or challenged (explained below); otherwise, after a timeout, the exit can be completed and funds withdrawn from the contract.

Since each coins has a distinct owner, each user only needs to check exits that try to withdraw coins they own. There are two distinct exit challenges:

  1. Non-interactive: the exit is spending an spent coin, and you can prove you owned this coin at a later time, within a timeout.
  2. Interactive: the exit is spending a coin that you currently own. This is challenged by asking for a valid spend for your currently-owned coin (which should not exist). This can be responded to or not, within a timeout.

In order to always be able to respond to the challenges above, the entire history of a coin (in the form of inclusion and exclusion proofs) must be kept, for each coin. This can quickly become enormous. Attempts to compress this history have led to research dead ends, or involve simply posting transaction data on-chain (i.e. degenerate to a rollup).

Users must verify their coins haven't been exited at least once per timeout to ensure safety. However, due to the interactive challenge, watchtowers cannot be used instead, so users must do this themselves.

Metrics:

  1. Security is borrowed from the main chain, as Plasma blocks are timestamped by Ethereum.
  2. Plasma Cash variants are trust-minimized in the same way as channels, with a synchrony assumption on main chain censorship.
  3. Plasma Cash is a Plasma and thus permissioned.
  4. Throughput can be substantially higher than Ethereum, as each user only needs to validate the history of coins they own, not the entire Plasma chain.
  5. Scalability is conditional on the number of coins a user owns. If the user owns sufficient coins, it might be more expensive to download and validate inclusion and exclusion proofs than fully validating Ethereum.
  6. Additional concerns:
    • No need for hot keys.
    • Since data is not available on-chain, users must save a copy of their off-chain state. Private key backups are insufficient to ensure funds cannot be lost.
    • Capital efficient for both users and the operator.
    • Latency cannot be lower than Ethereum, as Plasma blocks are only valid once they are committed to Ethereum.
    • Ethereum-style smart contracts are not possible on Plasma. Predicate spending conditions on UTXOs are possible.

In summary, Plasma Cash is trust-minimized, unlike Plasma MVP. However, it is permissioned, requires enormous off-chain cost to transfer coin history, and requires users to store state lest they lose their funds. As such, it is unsuitable as a scaling solution.

Validium

Validium is a Plasma-like protocol that uses validity proofs instead of fraud proofs and a synchrony assumption. In plain English:

  • Validity proof: succinct (exponentially cheaper to verify) proof that some computation has a certain result.
  • Fraud proof: proof that the result of some computation was incorrect. Requires a synchrony assumption (i.e. a timeout), after which the result is deemed correct if no fraud proof was submitted.

Validium has the benefits over Plasma of not having to worry about the operator producing an invalid block, and being resistant to miner censorship attacks for state safety. This means Validium blocks can be considered immediately finalized, while Plasma blocks are only finalized after the fraud proof timeout has expired. However, the production of validity proofs is expensive and has higher latency, involves novel and hard-to-audit cryptography, and may require a trusted setup.

There is one issue: how do we handle exits? Sure, the operator cannot steal user funds, but they can lock/burn user funds. We must always analyze both state liveness and state safety. In a Validium, as in a Plasma, block data is kept off-chain. As such, users may not have the state at the latest block, which would be required to exit seamlessly. We have a few choices:

  1. Users initiate an exit on Ethereum, and the operator must process it with a validity proof within some timeout (or provide a proof that the exit is invalid), otherwise the Plasma chain halts. This does not work, as it does not guarantee users can exit since the operator can simply stop processing anything.
  2. Users initiate an exit on Ethereum, and if it is not processed by the operator within some timeout, it is force-processed, unless proven invalid with a fraud proof. This means a user can get an invalid exit processed, which defeats the point of using validity proofs.
  3. Users initiate an exit on Ethereum, and if it is not processed by the operator within some timeout, the Validium chain rolls back to a previous block. This means we no longer have immediate finality. If we allow unbounded roll back, then there are no finality guarantees ever (which is unacceptable). If we allow bounded roll back, then that introduces a synchrony assumption which can be used to steal user funds.

Surprisingly, there is no way to ensure state liveness without re-introducing a synchrony assumption, which makes using validity proofs worse than useless!

Note: Synchrony assumptions can often be obfuscated. From the last link above: "The assumption is that we can recover to a state based on a history that took place not more than k batches ago." This k is a synchrony assumption, and user funds can be stolen if it is violated.

In summary, Validium does not actually exist as a separate cryptoeconomic system than Plasma. The use of validity proofs is superfluous, and increases latency and cost for no concrete benefit.

Rollups

Different flavors of rollups have many common features:

  • Anyone may construct a rollup block (permissionless), and post the entire block to an Ethereum smart contract as calldata. The rollup block is not validated in the contract; instead, block validity is ensured by either a validity proof (ZK rollups) or a fraud proof + synchrony assumption (optimistic rollups). Since rollup block data is available, we can guarantee that block production can always be permissionless, unlike Plasma.
  • Withdrawals are initiated on the rollup chain, not Ethereum. Because of this, it is critical that anyone can have a transaction included into the rollup chain in bounded time. This means there is no requirement to monitor and challenge individual exits as in Plasma.
  • Rollups borrow security from Ethereum, as all rollup blocks are timestamped on Ethereum. As such, the latency of a rollup chain cannot be lower than Ethereum's blocktime.
  • Rollups may provide conditional scalability if multiple instances of a rollup (or multiple different rollups) are used, essentially as a form of heterogenous sharding. How much scalability this provides depends entirely on how often cross-rollup (similar to cross-shard) interactions happen.

Note: An alternative to permissionless block production is permissionless withdrawals only (but permissioned block production). This only works by halting the rollup chain if a withdrawal request isn't processed within some timeout. While technically trust-minimized, this is sub-optimal: exiting from the rollup may be very expensive, especially if the rollup has a lot of state.

ZK Rollups

ZK rollups were first concretely proposed by Barry Whitehat, and later refined and simplified.

In ZK rollups, each rollup block posted to the contract must be accompanied by a validity proof (a succinct proof that the block is valid), which is also verified by the contract. Blocks are thus finalized immediately, and withdrawals can be processed in the same Ethereum block. The block posted as calldata only includes sufficient information to apply each state transition (transaction). Witness data (signatures) can be excluded, as the validity proof proves that such signatures exist and are valid.

Users must do the following work to ensure their funds are safe:

  • Audit the circuit, which involves novel cryptography.
  • If a trusted setup is used, participate in the trusted setup, or assume the trusted setup was not compromised.
  • Fully validate Ethereum.
    • This includes downloading all state transitions.
    • Also includes verifying a validity proof for all rollup blocks. Individual state transitions do not need to be validated.

However, we must analyze the work users must do in the worst case, not the best case. In the worst case, the rollup block producers all go offline and the user must produce their own block. In other words, we must consider both state liveness and state safety, not only state safety.

To produce a new block, a user must re-apply every state transition in the rollup to get the latest state. This is actually the whole point of posting transactions to Ethereum! Notice that while the validity proof proves that a state transition is valid, it does not help at all in applying the state transition (which involves database reads and writes). In other words, the "succinct verification" property of validity proofs only applies to computation with no side effects, not state transitions.

Note: ZK rollups can only handle transfers and simple variants (such as multi-user transfer and swaps). This is because validity proofs operate on circuits rather than programs, and every possible execution path must be proved at all times. The cost of proving generic computation is intractable in practice, and has never been demonstrated to be viable.

As ZK rollups are limited to transfers, the only computation the validity proof can reduce is signature verification. However, this is not even remotely the bottleneck; modern processors can easily verify tens of thousands of ECDSA signatures per second with libsecp256k1. The bottleneck is actually state accesses. In other words, since they do not target the bottleneck, validity proofs in this context provide essentially zero scalability.

Metrics:

  1. Security is borrowed from the main chain, as rollup blocks are timestamped by Ethereum.
  2. ZK rollups are trust-minimized under the assumptions: 1) that the novel cryptography is sound, and has been implemented correctly despite immature tooling and few qualified auditors, 2) if a trusted setup is used, that the trusted setup was not compromised.
  3. Rollups are permissionless.
  4. ZK rollups will have higher throughput than Ethereum.
  5. Scalability is increased, as the data and execution model is much simpler than Ethereum.
  6. Additional concerns:
    • No need for hot keys.
    • Private key backup is sufficient to recover funds.
    • Novel cryptography must be audited by user, or trusted.
    • Capital efficient.
    • Latency cannot be lower than Ethereum, as rollup blocks are only valid once they are committed to Ethereum. ZK rollup blocks are immediately finalized once committed, however due to proof generation times take several minutes or more to be committed.
    • Anything but simple transfer are not possible on ZK rollups.

Optimistic Rollups

In optimistic rollups, each rollup block posted to the contract must be accompanied by the bond. Blocks are finalized after a timeout, at which point the bond can be recovered. The block posted as calldata must include both state transition information and witness data, unlike ZK rollups.

Anyone may post a fraud proof against a non-finalized block, causing the rollup chain to roll back to the previous block (and burning a fraction of the bond while rewarding the rest to the fraud prover). Therefore, on-chain finality requires waiting until the timeout expires. Valid blocks are guaranteed to finalize, however, so anyone may validate the rollup chain to get immediate finality for off-chain applications (e.g. accepting a payment, or performing an atomic swap).

Fungible tokens can be withdrawn immediately with atomic swaps between the rollup and Ethereum using a liquidity provider. Note that non-fungible tokens (NFTs) by definition cannot be liquid, therefore withdrawing NFTs from an optimistic rollup must wait the finalization timeout.

There is no inherent restriction on the execution model of an optimistic rollup, so long as a compact fraud proof can be produced, and verifying a fraud proof can be done within a single Ethereum block (modulo splitting up a fraud proof into several non-interactive steps). However, accounts-based execution models will have much lower scalability than Ethereum today.

This is because fraud proofs for accounts-based execution models require serializing the state after every transaction (or greatly increasing the size of transactions, rendering it strictly inferior to simply using the UTXO data model to begin with). Ethereum used to do this, but no longer does since serializing the state is, as the name implies, a sequential (serial) operation, and in general is a huge bottleneck for Ethereum today. The UTXO data model, in addition to parallel transaction validation, allows for fraud proofs without serializing state, resulting in greatly increased scalability.

Metrics:

  1. Security is borrowed from the main chain, as rollup blocks are timestamped by Ethereum.
  2. Optimistic rollup are trust-minimized under the assumptions: 1) 51% of miners are not censoring Ethereum for an extended period of time, and 2) there is at least one interested party that will submit a fraud proof if a fraudulent rollup block is committed (which could be the user themselves).
  3. Rollups are permissionless.
  4. Optimistic rollups may have higher throughput than Ethereum, depending on execution model.
  5. Scalability may be increased over Ethereum, depending on execution model. An EVM-based optimistic rollup will result in zero scalability improvements (and in fact scalability will be much worse). A UTXO-based optimistic rollup will be much more scalable than Ethereum.
  6. Additional concerns:
    • No need for hot keys.
    • Private key backup is sufficient to recover funds.
    • No novel cryptography.
    • Capital efficient.
    • Latency cannot be lower than Ethereum, as rollup blocks are only valid once they are committed to Ethereum. Valid optimistic rollup blocks are immediately finalized once committed, if clients validate them immediately (allowing immediate off-chain interactions, such as atomic swaps). Otherwise, the timeout to finality can be quite long (for on-chain interactions).
    • Virtually anything can be done in an optimistic rollup, so long as a compact fraud proof can be produced.

Interactive Verification Games

An alternative to fraud proofs is an interactive verification game, in which an interactive game bisects an execution trace until a difference is found. This bounds the maximum cost of a single step of this game to the cost of executing a single step of the trace, unlike fraud proofs where an entire transaction must be executed.

Beyond this distinction, there is little cryptoeconomic difference between optimistic rollups with fraud proofs and interactive verification games. Interactive verification games allow for more flexible execution models (e.g. a single transaction in the rollup that consumes 20 million gas), but result in lower scalability than using a UTXO-based execution model directly (or possibly even worse scalability than Ethereum) since a special virtual machine must be used to execute transactions off-chain. This virtual machine must be designed to have cheap individual operations, but is difficult to optimize for performance.

Note: In theory, an interactive verification game will be more robust against chain congestion attacks that fraud proofs. For example, if a fraud proof costs 9M gas to verify and the block gas limit is 10M, an attacker only needs to fill up blocks up to 1.1M gas to prevent fraud proof transactions from being included. Each step of the interactive verification game is very cheap, so an attacker would need to spam much more to block them from being included. In practice however, the cost of any individual fraud proofs should be intentionally low (less than 20% of the block gas limit or so), so there is no difference.

Note: When using fraud proofs, valid blocks are guaranteed to finalize, as they cannot be proven fraudulent. When using an interactive verification game, a valid block could be rolled back if a challenge on a valid block is intentionally lost. In practice this is not an issue as it requires unanimous participation to throw a challenge.

In summary, if more flexibility is absolutely required (e.g. single huge transactions that cannot be verified with a fraud proof), then an interactive verification game is a good idea. If not, a UTXO-based optimistic rollup with fraud proofs will be more scalable with no downsides.

Optimistic vs ZK Rollups

In terms of trust-minimization, users will have to make orthogonal trust assumptions for ZK rollups and optimistic rollups:

  • ZK rollups
    • that the novel cryptography is sound, and has been implemented correctly despite immature tooling and few qualified auditors
    • if a trusted setup is used, that the trusted setup was not compromised
  • optimistic rollups
    • 51% of miners are not censoring Ethereum for an extended period of time
    • there is at least one interested party that will submit a fraud proof if a fraudulent rollup block is committed (which could be the user themselves)
      These assumptions aren't directly comparable, so we cannot really say that one makes stronger assumptions than the other.

It may be tempting to say that miners can steal funds from an optimistic rollup but not a ZK rollup, but that ignores that layer-2 schemes do not exist in vacuo. If a majority of miners can censor Ethereum for an extended period of time, then they can also freeze user funds, which allows them to steal funds indirectly through blackmail.

Along the same vein, a common talking point in favor is ZK rollups is that a user can walk away for a decade and come back to their coins where they left them...but this is actually not true, even with ZK rollups! It is entirely possible that the layer-1 blockchain itself gets re-organized or otherwise mutated such that a user's funds in a ZK rollup–or even on the layer-1 chain–disappear. Decentralized blockchain are nothing more than mechanisms to facilitate social coordination; they are governed by people, not math or code. If a user does not actively watch the layer-1 chain (and the layer-2 with it) at regular intervals, then they cannot defend it–and their funds–from potential adversaries. In other words, regardless of what layer-2 scheme we use, we can assume that the base blockchain provides censorship resistance, because if not we must actively defend it in a timely manner.

In addition, we must consider the following facts:

In terms of scalability, as mentioned in the analysis of ZK rollups, the specific use of validity proofs provides no scalability. We must consider the worst-case failure mode of all the ZK rollup block producers disappearing or otherwise refusing to produce new blocks, at which point users must apply all state transitions themselves, for which the validity proofs do not help at all. Signature verification is not a bottleneck, so it's all a wash between validating signatures and verifying the validity proofs.

Validity proofs are useful for safe low-latency light clients, which fraud proofs cannot provide as their timeout must be long. This allows for immediate withdrawals (especially of NFTs) and composable interaction between a ZK rollup and other systems (similar to an atomic cross-shard transactions). However, since ZK rollups are limited to transfers and simple variations thereof, the practical usability of composable interactions is dubious.

In terms of on-chain efficiency (i.e. the marginal size of each transaction that must be posted as calldata), witness size for optimistic rollups is not a problem long-term. Rather than using per-transaction signatures, BLS aggregate signatures can be used instead, reducing the on-chain footprint of witness data to constant size per block. Interestingly, optimistic rollups are even more gas-efficient than ZK rollups, as the latter require the validity proof to be posted and verified as well.

To wrap up, the assumptions under which both optimistic rollups and ZK rollups are trust-minimized aren't comparable, and thus saying that one is inherently more trust-minimized than the other would be intellectually dishonest. ZK rollups provide zero scalability gains in practice over optimistic rollups, are more limited in their expressivity, are more expensive to use, and have higher latency-to-first-confirmation. On the other hand, optimistic rollups can only support immediate withdrawals of fungible assets, so they are cumbersome to use with NFTs.

In summary, if using NFTs is critical, then ZK rollups are the better choice. For everything else, UTXO-based optimistic rollups are superior: cheaper, lower latency-to-first-confirmation, more expressive, more scalable.

Rollup Mythbusting

Using PoS for block production is a good idea! False. Staking a token is worse-than-useless, as 1) we don't need a native token for security, since we're borrowing security from Ethereum, and 2) we can't implement a proper PoS protocol without hard forks, which cannot be done with a smart contract. A token simply adds costs and friction and provides nothing in return that cannot be done with ETH. Fuel does not have, and will never have, a "utility" token.

Rollup transactions are gas-free! False. Transactions in a rollup still require metering in order to prevent DoS attacks, and to ensure that invalid transactions are fraud-provable. If the execution model of the rollup is the EVM or some other generic VM, then rollup transactions will have to be metered similarly. Fuel currently meters transactions based on size (in bytes), as generic smart contracts are not supported at this time.

Note: The above exclamation is actually half-true. Rollup transactions do not have to pay ETH fees, as the rollup block producer is the only party that must interact with Ethereum to progress the rollup. Therefore rollups can be thought of as meta-transaction systems, allowing users to pay fees in any token the rollup block producers accepts.

Rollup transactions are free (as in "free beer")! False. Each rollup transaction has a marginal cost that must be paid by the block producer as calldata. If a rollup advertizes free transactions, then they are being subsidized from elsewhere–meaning someone else is overpaying fees. Fuel does not advertize transactions as being free.

Rollup transactions are instant! False. Rollup blocks must be posted to Ethereum, therefore a rollup cannot have a lower blocktime than Ethereum. Without a fully-collateralized bond, an adversary can double-spend transactions in an unconfirmed rollup block and guarantee a profit. A system with a fully-collateralized bond would end up as a channel. Fuel makes the explicit choice of not having a bond, since that would increase costs without providing any cryptoeconomic guarantees. Instead, our priority aggregator model allows the Fuel operator to promise an unconfirmed transaction will be included with high reliability, completely non-custodially.

EVM rollups can do {1000, 10000, 100000, A(4,3)} TPS with no downsides! False. EVM execution is the bottleneck for Ethereum. Throughput cannot be increased beyond the current ~15 TPS without a fundamental sacrifice elsewhere. This could be reduced decentralization with increased full node costs, or an additional trust assumption if users simply don't fully validate the rollup. Both are unacceptable in a scaling solution. Fuel uses a unique UTXO-based execution model that is much more scalable than the EVM, allowing for unparalleled throughput without changing decentralization. This does come at a cost for now: no generic smart contracts (which will be implemented in Fuel v2).

ZK rollups scale because you only have to verify a succinct proof! False. Validity proofs are useless for applying state transitions, which is by far the bottleneck in contemporary blockchain architectures.

Conclusion

Fuel's UTXO-based optimistic rollup is uniquely suited as the best scaling solution for Ethereum available today. It is trust-minimized and permissionless, and provides high scalability along with a number of other desireable properties, the particular combination of which is unmatched.

See Also

  1. Scaling Ethereum with Rollups at EthDenver 2020
  2. Fuel - A Trustless Scalable Sidechain at EthCC 2020
  3. Fuel: Scaling Ethereum with Optimistic Rollups at NonCon 2020

File: src/v1.0.0/3. Concepts/0. Fundamentals/3. Block Architecture.md

Block Architecture

Being a UTXO-based blockchain, Fuel is designed with a number of similarities to existing UTXO-based chains, such as Bitcoin. Consult the Bitcoin developer reference for an overview of the minimal block layout required for such a chain.

Overview of block contents.

This page describes intuitions behind the top-level data structures, from blocks to leaves in the transaction tree. Discussion on the transaction architecture is relegated to the next page.

Block Header

In any blockchain, block headers commit to a list of transactions, and contain other important metadata. Blocks are block headers plus the explicit list of transactions, but do not exist as an independent data structure in Fuel.

Each block header in Fuel contains the usual suspects: an identifier for the block producer, the previous block header's hash, and the block height (the index of this rollup block in the rollup chain). A few other fields, that diverge from traditional blockchains, are also present.

First, the block number, i.e. the Ethereum block number when this rollup block was submitted to Ethereum, is included. Note that the expressions [block] height and [block] number are used to refer to the rollup block height and the Ethereum block number, respectively.

Note: Including the block number in the header (or, more specifically, requiring an Ethereum block number and Ethereum block hash to match when committing a new rollup block) prevents miners from re-organizing Ethereum and moving a submission transaction to an earlier block number, which could invalidate the timelock conditions of Fuel transactions.

Additionally, the maximum token ID and address ID used through the rollup block (i.e. in this and all previous rollup blocks) is included in the header. The transaction architecture page will discuss these in more details, but for now it is sufficient to know that addresses (token are identified by an address as well) can be registered, which allows them be to referenced by a short numerical ID rather than a full 20-byte address, resulting in smaller (and thus cheaper) transactions.

Note: An attacker can craft a rollup block that includes a transaction with a not-yet-registered ID (i.e. an invalid block). When they see a transaction that proves that block is invalid, they can front-run it with a transaction that registers the ID. Checking for maximum IDs as a block is submitted prevents this griefing vector.

Finally, rather than a single Merkle root of a list of transactions, a list of root header hashes is included. Each root header commits to a list of transactions (in other words, the block header still commits to a list of transactions, though indirectly), and is discussed in the next section.

Fields that are usually seen in layer-1 Proof-of-Work chains, such as a timestamp and a nonce, are omitted. The former is implicit in the form of the Ethereum block number, and the latter is unnecessary since work is not used in the rollup.

Root Header

Each root header commits to a list of transactions directly in two forms, and contains additional metadata that will be discussed shortly. Roots are root headers plus the explicit list of transactions, but do not exist as an independent data structure.

A list of transactions is committed to as both a Merkle root and a simple hash. The simple hash is checked when a new root is submitted on-chain, but not the Merkle root. This avoids the need to Merkleize transactions all the time, and saves on gas costs. If the Merkle root is incorrect, this can be proven with a fraud proof (which uses the simple hash to authenticate the data that will be Merkleized).

Note: Separating block submission into two steps—first submitting one or more roots, then submitting the block header that includes those roots—reduces the costs of a race condition where multiple block producers submit a block at the same time and only one is valid (and the other transactions would fail and cost gas). By making block submission extremely gas-cheap, this race condition is no a longer costly mistake. There is no race condition on submitting roots.

Note: The size bound on the list of transactions is chosen such that Merkleizing any individual list of transactions can be done within the gas limit of a single Ethereum block (with a large margin of safety).

Two transaction fields are hoisted into the root header: the fee token ID and the feerate. By enforcing that all transactions under a single root pay fees in the same token at the same rate, we can avoid needing to repeat these values for every transaction, and instead only specify them once in the root header.

Note: Transactions still sign over the fee token ID and feerate values, to avoid malicious root submissions. The values are implicitly part of the transaction data that is signed. See the next section for implicit vs explicit transaction data.

Transaction Leaves

The transaction architecture can be found in the next page. It is recommended to read it first, before returning to complete this section.

Each transaction leaf (i.e. leaf in the Merkle tree of transactions that are committed to in a root header) contains all the required data to reconstruct the state transition defined by the transaction unambiguously.

  1. A list of witnesses, which authorize spending a state element in this transaction.
  2. A list of inputs and metadata (one metadata per input). Each input provides enough information to "unlock" the state element being spent, but does not explicitly identify the state element by ID.
  3. A list of outputs, which each describe the spending conditions for a new state element.

Metadata is inserted by the root submitter as a means of uniquely identifying the state element being spent at each input. For regular UTXOs this would be the block height, root index, transaction index, then output index. This replaces the traditional UTXO ID (usually: a hash of the transaction and output index) with a much more compact representation.

Note: Metadata is malleable by the root submitter, and is thus not signed over by the transaction sender.


File: src/v1.0.0/3. Concepts/0. Fundamentals/4. Transaction Architecture.md

Transaction Architecture

A transaction with up to eight inputs and eight outputs.

A transaction in Fuel specifies a state transition: inputs tell which state elements are consumed and outputs tell which state elements are produced. As with any UTXO-based system, each state element can only be produced and consumed at most once. Modeling the rollup chain's state as a key-value store, this means that keys can never be re-used, and keys commit to values in some way.

Note: Up to eight inputs and outputs are allowed per transaction, to keep the worst-case cost of fraud proofs low while simultaneously enabling a number of applications that require multiple inputs or outputs.

Each input specifies the state element to spend, and unlocks it. For plain UTXOs and deposits, this is the UTXO/deposit ID and a valid digital signature. For HTLC UTXOs, this is the UTXO ID and a preimage (if using the hashlock spending condition) and a valid digital signature.

Each output specifies the new state element to produce (including amounts, token type, etc.), and its spending conditions (recipient address, timelock and hashlock).

Transactions can be validated statelessly, and the state database only needs to be checked for the existence of each consumed state element. In addition, there is no inherent limitation on which accounts sign a transaction, so a transaction can represent an atomic interaction between more than one user (e.g. an on-chain atomic exchange).

The transaction ID (a unique identifier for each transaction) is computed as the EIP-712 hash of the hash of the transaction data without witnesses. We will see in the next section why excluding witness data has some nice properties.

Inputs: Witnesses and Metadata

Input witness and metadata.

Each input is linked to a witness (generalization of a digital signature) and metadata.

Witnesses are either a digital signature, or an authorization from a smart contract on Ethereum (e.g. a smart contract wallet can be used to authorize Fuel transactions). In either case, authorization is performed on a transaction ID (as the in previous section, the non-witness transaction data), i.e. signatures are over the transaction ID.

Note: Signing over the transaction ID means that the transaction only has to be hashed once when verified, regardless of the number of inputs or outputs. This avoids the quadratic hashing issue seen in Bitcoin.

Each input specifies a state element to consume (i.e. a UTXO ID, or a deposit ID), which is a 32-byte hash. This value is part of the non-witness transaction data, and is signed over. However, when posting transactions on Ethereum, explicit state element identifiers are stripped and replaced by more compact metadata, which "point" to a unique entry in the ledger (or a deposit). In other words, metadata will point to an exact output in the totally ordered outputs, transactions, roots, and blocks.

Note: Given the one-to-one mapping between ledger entries and state element identifiers, it is easy to show the state element produced by an exact entry with a simple inclusion proof, leveraging the property that computing state element identifiers is a stateless operation.

Note: Extending the above, transaction IDs are not immediately obvious with only the data posted to Ethereum, since state element identifiers are missing. However, they can be proven, and the transaction IDs computed, with inclusion proofs for the entires pointed to by metadata.

Segregated Witnesses

Segregated witnesses.

Fuel is designed with a form of segregated witness, where witness data is not bundled with inputs. While the previous sections provided intuition, they did not precisely describe where witnesses are placed in transactions.

Each input actually specifies a witness index that authorizes spending the referenced state element. This means that multiple inputs could use the same witness, which greatly reduces the cost of spending multiple state elements owned by the same account.

Simple Send Example

A simple send.

As an example, the above is a simple one-input two-output simple send. One output is returned to the sender as change (there is no special change output type, so it looks like any other output). The other is sent to the recipient.


File: src/v1.0.0/3. Concepts/0. Fundamentals/5. Serialization.md

Fuel EIP-712 Specification

This document describes the Fuel EIP-712 specification, including encodeTypes, and encodeData functions.
EIP-712 improves usability of off-chain message signatures for use on-chain by specifying a standard for hashing and signing of typed (semi-)structured data (not just bytes).

encodeTypes

Address

encodeData:

  • require Output.owner.length > 0
  • require Output.owner.length <= 20
  • Output.owner =
    • if Output.owner.length == 20 then Output.owner
    • else Output.owner[:Output.owner.length]

Input

text
Input(
    uint8 type,
    uint8 witnessReference
)

InputDeposit

text
InputDeposit(
    uint8 type,
    uint8 witnessReference,
    address owner
)

InputHTLC

text
InputHTLC(
    uint8 type,
    uint8 witnessReference,
    bytes32 preImage
)

Metadata

text
Metadata(
    uint32 blockHeight,
    uint8 rootIndex,
    uint16 transactionIndex,
    uint8 outputIndex
)

MetadataDeposit

text
MetadataDeposit(
    uint32 token,
    uint32 blockNumber
)

Output

text
Output(
    uint8 type,
    uint8 tokenLength,
    bytes1[] token,
    uint8 amountShift,
    uint8 amountLength,
    uint8[] amount,
    uint8 ownerLength,
    bytes1[] owner
)

encodeData for Output:

  • require amountLength <= 32
  • require amountShift <= 256
  • require ((amountLength * 8) + amountShift) <= 256
  • Output.amount = Output.amount.data[:Output.amountLength] << Output.amountShift

OutputHTLC

text
OutputHTLC(
    uint8 type,
    uint8 tokenLength,
    bytes1[] token,
    uint8 amountShift,
    uint8 amountLength,
    uint8[] amount,
    uint8 ownerLength,
    bytes1[] owner,
    bytes32 digest,
    uint32 expiry,
    uint8 returnOwnerLength,
    bytes1[] returnOwner
)

OutputReturn

text
OutputReturn(
    uint8 type,
    uint16 dataLength,
    bytes1[] data
)

Storage

Base type for stored types. Computes index into storage of 1-, 2-, or 3-element items:

  • mapping key for 1-element item is: keccak256(0, 64)
  • mapping key for 2-element item is: keccak256(0, 96)
  • mapping key for 3-element item is: keccak256(0, 128)

TransactionProof

text
TransactionProof(
    address blockProducer,
    bytes32 previousBlockHash,
    uint256 blockHeight,
    uint256 blockNumber,
    uint256 numTokens,
    uint256 numAddresses,
    uint16 rootsLength,
    bytes32[] roots,
    address rootProducer,
    bytes32 merkleTreeRoot,
    bytes32 commitmentHash,
    uint256 rootLength,
    uint256 feeToken,
    uint256 fee,
    uint16 rootIndex,
    uint16 merkleProofLength,
    bytes32[] merkleProof,
    uint8 inputOutputIndex,
    uint16 transactionIndex,
    uint16 transactionLength,
    bytes1[] transaction,
    uint8 dataLength,
    bytes32[] data,
    uint256 signatureFeeToken,
    uint256 signatureFee,
    address tokenAddress,
    address selector
)

TransactionLeaf

text
TransactionLeaf(
    uint16 length,
    uint8 metadataLength,
    bytes8[] metadata,
    uint16 witnessesLength,
    bytes1[] witnesses,
    uint16 inputsLength,
    bytes1[] inputs,
    uint16 outputsLength,
    bytes1[] outputs
)

UTXO

text
UTXO(
    bytes32 transactionId,
    uint256 outputIndex,
    uint256 outputType,
    bytes32 owner,
    uint256 amount,
    uint256 token,
    bytes32 digest,
    uint256 expiry,
    bytes32 returnOwner
)

Witness

There is no explicit Witness type, only its sub-types of Signature, Caller, and Producer.

text
Signature(
    uint8 type,
    bytes32 r,
    bytes32 s,
    uint8 v
)
text
Caller(
    uint8 type,
    address owner,
    uint32 blockNumber
)
text
Producer(
    uint8 type,
    bytes32 hash
)

Special notes

`chainID`

Fuel does not mandate the use of a chainID field.

Applications can rely on existing secure chainID approaches, including EIP-155 Simple replay attack protection and EIP-1344 ChainID opcode. EIP-155 includes a chainID in the computation of a transaction's signature, and EIP-1344 defines a chainID smart contract opcode.

An important sysops note is that the use of EIP-1344 in Fuel is fully backwards compatible with all chains which implement EIP-155 chain ID domain separator for transaction signing.

References

Vitalik Buterin, "EIP-155: Simple replay attack protection," Ethereum Improvement Proposals, no. 155, October 2016. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-155.

Richard Meissner, Bryant Eisenbach, "EIP-1344: ChainID opcode," Ethereum Improvement Proposals, no. 1344, August 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1344.


File: src/v1.0.0/3. Concepts/0. Fundamentals/6. System Description Primer.md

System Description Primer

System Components

This page lists the different system components that are documented in the remainder of the Concepts section.

Data Structures

The core data structures are:

  1. Addresses: a registry for user account addresses.
  2. Blocks and block headers: top-level data structure.
  3. Deposits: deposits to Fuel from Ethereum.
  4. Inputs: transaction inputs.
  5. Metadata: point to state elements to consume.
  6. Outputs: transaction outputs.
  7. Roots and root headers: commit to a batch of transactions.
  8. Tokens: a registry for token addresses.
  9. Transactions: transactions.
  10. Withdrawals: withdrawals from Fuel to Ethereum.
  11. Witnesses: authorize transactions.

Verifiers

If a fraud proof is submitted to the contract, it first needs to be validated for correct formatting. This is required to ensure that a maliciously-constructed fraud proof can never be processed, otherwise a valid block could be reverted. Parsing fraud proofs is handled by verifiers.

Provers

Once a fraud proof has successfully been parsed by the appropriate verifier modules, it must be processed to prove that fraud occurred (or that the fraud proof is properly formatted but otherwise incorrect). This is handled by provers.

Additional Modules

Outside the core components above, additional optional modules are provided to e.g. improve UX and add more functionality:

  1. Token funnels: ephemeral contracts that accept token deposits without an approve step.

File: src/v1.0.0/3. Concepts/0. Fundamentals/7. Deployment Parameters.md

Deployment Parameters

Constructor Parameters

Here we will describe the Fuel contract's constructor parameters.

field name description
operator address; The primary operator of the Fuel rollup. This account receives priority access on including roots in blocks production, however, anyone else may use roots with a delay specified by the submissionDelay parameter.
finalizationDelay uint256; The time to finalization in Ethereum blocks.
submissionDelay uint256; The number of Ethereum blocks a non-operator must wait before using a root in a block.
penaltyDelay uint256; The delay which the operator will face if they commit Fraud, until the time they may come back online as the primary operator.
bondSize uint256; The bond size required for block production, in wei.
name string; The name string used for contract identification and EIP-712 hashing.
version string; The version of the contract used for identification and EIP-712 hashing.
chainId uint256; The Ethereum network chain identifier used for identification, replay attack protection, and EIP-712 hashing.
genesis bytes32; The registered genesis block hash. This property is simply for vanity and can be set to any value as the minimum referenced block is always > 0.

Deploying in Operated Mode

If you would like to deploy a Fuel chain in operated mode, use non-zero values for operator and submissionDelay.

Deploying in Un-operated Mode

If you would like to deploy Fuel in un-operated mode, where anyone may submit blocks at any time, use zero values for operator and submissionDelay.

Finalization Delays

In any optimistic rollup, finalization delay is a critical security parameter. A longer value provides more robustness against miner censorship attacks, but will result in longer withdrawals.

We recommend 1 week at the minimum, or 2 weeks on the higher end, to protect against both chain congestion attacks and censorship attacks.

Rinkeby Testnet

Contract address: 0x669c63dDD6050838faf496F46EEb7378bff05d77

Name: rinkeby

field name value
operator 0x0ea6b5edc8905c85514b3676703f1bfe6ec260ad
finalizationDelay 93046 (one week)
submissionDelay 6646 (one day)
penaltyDelay 0 (zero)
bondSize 100000000000000000 (0.1 ETH)
name Fuel
version 1.0.0
chainId 4
genesis 0x0000000000000000000000000000000000000000000000000000000000000000 (empty hash)

File: src/v1.0.0/3. Concepts/1. Data Structures/Addresses.md

Addresses

The address registry maintains a mapping of address (20 bytes) to address ID (unsigned integer up to and including 19 bytes). Each time a new address is registered, it is assigned the next address ID in sequence.

Note that registering an address is not necessary to send or receive funds on Fuel. It is a purely optional way of reducing transaction sizes (and thus costs).


File: src/v1.0.0/3. Concepts/1. Data Structures/Blocks.md

Blocks

BlockHeader

name type size description
producer address 20 Address of block proposer committing this block.
previousBlockHash bytes32 32 Previous block's header hash.
height uint256 32 Block height.
blockNumber uint256 32 Ethereum block number when this block is committed.
numTokens uint256 32 Maximum token ID used through this block.
numAddresses uint256 32 Maximum address ID used through this block
roots.length uint16 2 Number of transaction roots.
roots bytes32[] 32* List of root header hashes.

Number of Tokens

Tokens IDs are registered contract-side to allow for more compact transactions. The maximum token ID used in this block and all previous blocks is included in the block header.

This is needed to prevent a griefing attack where a fraudulent rollup block is committed that spends a token ID that is not yet registered. The fraud proof can then be front-run with a transaction that registers the token ID, thereby invalidating the fraud proof.

See: token registry.

Number of Addresses

Address IDs are registered contract-side to allow for more compact transactions. The maximum address ID used in this block and all previous blocks is included in the block header.

This is needed to prevent a griefing attack where a fraudulent rollup block is committed that sends to an address ID that is not yet registered. The fraud proof can then be front-run with a transaction that registers the address ID, thereby invalidating the fraud proof.

See: address registry.

Roots

The block header includes a list of root header hashes, roots. Each individual root header hash is the hash of a root header, which commits to a list of transactions and other important metadata. For more information of why multiple roots are used instead of the more traditional single-transactions-root, see Block Architecture.

The number of root header hashes is upper-bounded by the TRANSACTION_ROOTS_MAX parameter (128).

Committing a New Block Header

When a new block header is produced and committed to Ethereum, the following checks must pass, and are enforced by the smart contract.

  1. The height blockHeight must be greater than 0 and at most the tip height.
  2. The previous block hash previousBlockHash must match the previous block hash at blockHeight.
  3. The number of transaction roots roots.length must be greater than 0 and at most TRANSACTION_ROOTS_MAX.
  4. Every root in roots must exist in the root registry.
  5. If the operator is not in penalty mode (discussed below):
    1. If the block producer producer is not the operator:
      1. At least the submission delay parameter Ethereum blocks must have elapsed since the root was submitted.

The penalty mode becomes active if a successful fraud proof against an operator-produced block is processed. It remains active for the penalty delay parameter Ethereum blocks.


File: src/v1.0.0/3. Concepts/1. Data Structures/Deposits.md

Deposits

The deposit registry uniquely indexes deposits so they can be spent on Fuel.

Deposit

name type size description
owner address 20 Owner of deposit.
token uint256 32 4-byte Token ID.
blockNumber uint256 32 Ethereum block number deposit was made.
value uint256 32 Amount of tokens.

Deposits are uniquely keyed by { owner, token, blockNumber }, with value value.

Since the granularity of deposits is in Ethereum blocks, multiple deposits from the same owner of the same token in the same Ethereum block simply update the deposited amount.

Deposits can be spent no earlier than in a Fuel block committed in the next Ethereum block.


File: src/v1.0.0/3. Concepts/1. Data Structures/Inputs.md

Inputs

Inputs indicate which state elements (usually: UTXOs) are consumed by a transaction. Each state element can be consumed and produced at most once.

InputType

text
enum InputType : uint8 {
    Transfer = 0,
    Deposit = 1,
    HTLC = 2,
    Root = 3,
}

Input

name type size description
type InputType 1 InputType.Transfer or InputType.Root.
witnessIndex uint8 1 Index of witness that authorizes spending the state element.

There are no spending conditions on plain transfer UTXOs and root UTXOs (collected fees) other than a valid signature from the owner of the UTXO.

InputDeposit

name type size description
type InputType 1 InputType.Deposit.
witnessIndex uint8 1 Index of witness that authorizes spending the deposit.
owner address 20 Deposit owner address.

There are no spending conditions on deposits other than a valid signature from the owner of the UTXO. The owner of the deposit must be provided as it is needed to compute the unique deposit ID.

InputHTLC

name type size description
type InputType 1 InputType.HTLC.
witnessIndex uint8 1 Index of witness that authorizes spending the state element.
preImage bytes32 32 Preimage of digest if spending through hashlock.

HTLC UTXOs require a valid signature from the owner of the UTXO. In addition, if using the hashlock condition, the preimage must be provided. If using the timelock condition, HTLC UTXOs are only spendable after the output's timelock has expired.


File: src/v1.0.0/3. Concepts/1. Data Structures/Metadata.md

Metadata

Each transaction can have metadata attached to it by the block producer. One metadata is attached per input. This allows for fraud proofs without state commitments. (See also: Compact Fraud Proofs for UTXO Chains Without Intermediate State Serialization.)

Note that all metadata objects are exactly 8 bytes.

Metadata

name type size description
blockHeight uint32 4 Rollup block height.
rootIndex uint8 1 Root index in block.
transactionIndex uint16 2 Transaction index in root.
outputIndex uint8 1 Output index in transaction.

Metadata uniquely identifies an exact output in the ledger, first by the block height, then the root, transaction index, and finally output index. Essentially, metadata "references" an exact output. This allows for both compact fraud proofs without serializing the state, leveraging the property that UTXOs are produced and consumed at most once, and smaller transaction payloads than using UTXO IDs directly (a 32-byte hash).

MetadataDeposit

name type size description
token uint32 4 Token ID.
blockNumber uint32 4 Ethereum block number.

Deposit metadata partially uniquely identifies a deposit. The last part of the deposit registry key is the owner address, which can be recovered from the witness (of valid transactions).


File: src/v1.0.0/3. Concepts/1. Data Structures/Outputs.md

Outputs

Outputs define which state elements (usually: UTXOs) are produced by a transaction. Each state element can be produced and consumed at most once.

OutputType

text
enum OutputType : uint8 {
    Transfer = 0,
    Withdraw = 1,
    HTLC = 2,
    Return = 3,
}

Output

name type size description
type OutputType 1 OutputType.Transfer or OutputType.Withdraw.
token.length uint8 1 Next field number of bytes.
token bytes1[] 1* The token ID (<= 4 bytes). See: token registry.
amount.shift uint8 1 Amount shifted bytes. See: serialization.
amount.length uint8 1 Next field number of bytes.
amount uint8[] 1* Amount of tokens. See: serialization.
owner.length uint8 1 Next field number of bytes.
owner bytes1[] 1* Either address ID (<= 19 bytes) or address (= 20 bytes) of recipient. See: address registry.

A simple transfer between two users on Fuel or a withdrawal from Fuel back to Ethereum.

OutputHTLC

name type size description
type OutputType 1 OutputType.HTLC.
token.length uint8 1 Next field number of bytes.
token bytes1[] 1* The token ID (<= 4 bytes). See: token registry.
amount.shift uint8 1 Amount shifted bytes. See: serialization.
amount.length uint8 1 Next field number of bytes.
amount uint8[] 1* Amount of tokens. See: serialization.
owner.length uint8 1 Next field number of bytes.
owner bytes1[] 1* Either address ID (<= 19 bytes) or address (= 20 bytes) of recipient. See: address registry.
digest bytes32 32 Hashlock digest. The preimage to this digest must be revealed to spend this output.
expiry uint32 4 Timelock as an Ethereum block number.
returnOwner.length uint8 1 Next field number of bytes.
returnOwner bytes1[] 1* Either address ID (<= 19 bytes) or address (= 20 bytes) of owner if HTLC expires. See: address registry.

The timelock expiry is an absolute Ethereum block number. Any rollup block committed at or after this Ethereum block number can spend the UTXO with the timelock condition.

See also: HTLC explainer on Bitcoin Wiki.

OutputReturn

name type size description
type OutputType 1 OutputType.Return.
data.length uint16 2 Next field number of bytes.
data bytes1[] 1* Data blob. Between 1 and 512 bytes, inclusive.

Return outputs are provably prunable, i.e. they are not inserted into the state (the UTXO set). They can be used to record and timestamp arbitrary information to the Fuel chain, useful for building applications with client-side execution.

See also: Bitcoin's OP_RETURN.

UTXO

name type size description
transactionId bytes32 32 Transaction ID, the hash of the serialized unsigned transaction data.
outputIndex uint256 32 Index of output in transaction.
outputType uint256 32 Output's type.
owner bytes32 32 Owner of UTXO, either a raw address or an address ID.
amount uint256 32 Amount of tokens.
token uint256 32 Token ID.
digest bytes32 32 HTLC only: hashlock.
expiry uint256 32 HTLC only: timelock.
returnOwner bytes32 32 HTLC only: return owner.

The UTXO ID is simply the hash of the UTXO.

The HTLC-only fields are set to zero (0) if the output is not an HTLC output.


File: src/v1.0.0/3. Concepts/1. Data Structures/Roots.md

Roots

RootHeader

name type size description
rootProducer address 20 Address of root producer.
merkleTreeRoot bytes32 32 Claimed Merkle root of list of transactions.
commitmentHash bytes32 32 Simple hash of list of transactions.
rootLength uint256 32 Number of roots.
feeToken uint256 32 Token ID of all fees paid in this root.
fee uint256 32 Feerate of all fees paid in this root.

Each root header commits to a list of transactions. Each root is a root header and a list of transactions (and does not exist as a canonical data structure). Blocks contain one or more root header hashes (i.e. hashes of root headers), and so implicitly commit to a list of transactions. For more information of why multiple roots are used instead of the more traditional single-transactions-root, see Block Architecture.

When committing a new root to the contract, the root header minus the commitmentHash is provided, along with a list of transactions. The commitmentHash is then computed by simply hashing the list of transactions. This means that root headers claim a Merkle root of their transactions, which is assumed to be correct optimistically to save gas. If the claimed Merkle root is incorrect, a fraud proof can be constructed to show this.

The maximum size in bytes of the list of transactions in a single root is the MAX_ROOT_SIZE parameter (57600). This limit is placed to ensure that the transactions can always be Merkleized within the gas limit of a single Ethereum block in a fraud proof.

The maximum number of transactions in a single root is the MAX_TRANSACTIONS_IN_ROOT parameter (2048).


File: src/v1.0.0/3. Concepts/1. Data Structures/Tokens.md

Tokens

The token registry maintains a mapping of token contract address (20 bytes) to token ID (unsigned integer up to and including 4 bytes). Each time a new token is registered, it is assigned the next token ID in sequence.

New tokens deposited to the contract are automatically assigned a new token ID without any additional interaction.

Native ether (ETH) has token ID 0.


File: src/v1.0.0/3. Concepts/1. Data Structures/Transactions.md

Transactions

Transactions consume inputs and produce outputs.

UnsignedTransaction

name type size description
inputs.length uint16 2 Next field number of bytes.
inputs bytes1[] 1* List of inputs.
outputs.length uint16 2 Next field number of bytes.
outputs bytes1[] 1* List of outputs.
data.length uint8 1 Next field number of elements.
data bytes32[] 32* Implicit unique identifier of what to spend.
signatureFeeToken uint256 32 Implicit fee token ID.
signatureFee uint256 32 Implicit fee rate.

Transaction data that is signed off-chain. The unsigned transaction data is serialized then hashed to get a unique transaction ID, which is then signed over to get a witness. Essentially, each witness authorizes an entire transaction. This scheme avoids the quadratic hashing issue present in Bitcoin.

Implicit data (data) is one 32-byte hash per input, and uniquely identifies what is being spent by that input (a UTXO ID or a deposit ID).

The transaction ID is the EIP-712 hash of the hash of the serialized unsigned transaction data.

TransactionLeaf

name type size description
length uint16 2 Total length of leaf in bytes.
metadata.length uint8 1 Next field number of elements.
metadata bytes8[] 8* List of metadata, one per input.
witnesses.length uint16 2 Next field number of bytes.
witnesses bytes1[] 1* List of witnesses.
inputs.length uint16 2 Next field number of bytes.
inputs bytes1[] 1* List of inputs.
outputs.length uint16 2 Next field number of bytes.
outputs bytes1[] 1* List of outputs.

A leaf in a transaction Merkle tree, which is committed to in a root.

Note that the implicit data is not included in the leaf, as it can be reconstructed from metadata. Rather, the root producer replaces the larger data (state element) with the smaller metadata (a pointer to an entry in the ledger or a deposit).

Note that the implicit fee token ID and feerate are not included in the leaf. Since the value of those fields must be identical for all transaction under a root, they can be hoisted and declared once at the root header level.

The minimum transaction leaf size in bytes is set by the parameter TRANSACTION_SIZE_MIN (44).
The maximum transaction leaf size in bytes is set by the parameter TRANSACTION_SIZE_MAX (896).
The maximum number of inputs in a single transaction is INPUTS_MAX (8).
The maximum number of outputs in a single transaction is OUTPUTS_MAX (8).


File: src/v1.0.0/3. Concepts/1. Data Structures/Withdrawals.md

Withdrawals

Withdrawal Metadata

name padded type compact type size description
rootIndex uint256 uint32 4 Index of root in list of roots.
transactionLeafHash bytes32 bytes32 32 Hash of transaction leaf in tree rooted at rootIndex.
outputIndex bytes32 uint8 1 Index of output in list of outputs of transaction in the transaction leaf.

Withdrawal metadata is represented in padded form on Ethereum to simplify processing, and compact form on Fuel. The ID of a withdrawal is the hash of the padded-form withdrawal metadata.

A withdrawal registry keep track of whether a withdrawal has been processed or not (a Boolean value), indexed by the rollup block height and unique withdrawal ID. "Processed" withdrawals are completed withdrawals (i.e. the tokens have been withdrawn from the contract).


File: src/v1.0.0/3. Concepts/1. Data Structures/Witness.md

Witness

WitnessType

text
enum WitnessType : uint8 {
    Signature = 0,
    Caller = 1,
    Producer = 2,
}

Witnesses come in different types, depending on the origin of the authorization.

Signature

name type size description
type WitnessType 1 Witness type.
r bytes32 32 Signature r value.
s bytes32 32 Signature s value.
v uint8 1 Signature v value.

This is a normal signature that signs over the transaction ID to authorize.

See: EIP-155.

Caller

name type size description
type WitnessType 1 Witness type.
owner address 20 Caller address.
blockNumber uint32 4 Ethereum block number witness was registered at.

This witness type is for when a contract on Ethereum authorizes a Fuel transaction. One use is smart contract wallets, rather than externally-owned accounts, authorizing transactions.

Caller witnesses in the witness registry are indexed by owner and block number.

Producer

name type size description
type WitnessType 1 Witness type.
hash bytes32 32 Transaction ID being witnessed.

Block producers can spend fee outputs (i.e. Root outputs) in blocks they produce without an explicit signature.


File: src/v1.0.0/3. Concepts/2. Verifiers/Block Header.md

Verifier: Block Headers

Validates the block header component of a transaction proof is properly formatted.

  1. The hash of the block header must be the block hash at proof.blockHeader.blockHeight.
  2. If checking for finalizable block, then:
    1. The current block number must be at least the block number proof.blockHeader.blockNumber.
  3. If checking for not-finalizable block, then:
    1. The current block number must be less than the block number proof.blockHeader.blockNumber.

Additional checks must pass if the root header component of a transaction proof is also being validated at the same time.

  1. The root index proof.rootIndex must be less than the number of roots proof.blockHeader.roots.length.
  2. The hash of the root header proof.rootHeader must match the root header at index proof.rootIndex.

File: src/v1.0.0/3. Concepts/2. Verifiers/Data.md

Verifier: Data

Validates the data component of a transaction proof is properly formatted.

The data field in an unsigned transaction is a list of the unique state elements that are being consumed by the inputs. Since the key for these state elements (a hash) commits to sufficient information to verify their values, we simply need to provide the preimage to each hash.

For each provided state element stateElement and each data data in the list of proof data proof.data:

  1. The hash of stateElement must match data.

Note that stateElement can come in different types, depending on the type of the input (i.e. what is being spent).


File: src/v1.0.0/3. Concepts/2. Verifiers/Inputs.md

Verifier: Inputs

Validates the inputs component of a transaction proof is properly formatted.

One additional proof per input in the primary transaction proof proof must be provided, the list inputProofs.

For each input in proof.transaction.inputs and inputProof in inputProofs, indexed by index:

  1. If input's type is Transfer or HTLC:
    1. The transaction proof inputProof must be validated.
    2. The output metadata of inputProof must match the metadata of input (i.e. they must be pointing to the same entry in the ledger).
    3. The UTXO ID being produced by the output of inputProof must match proof.data[index].
  2. If input's type is Deposit:
    1. The deposit object inputProof must be validated:
      1. The deposit ID of inputProof must match proof.data[index].
  3. If input's type is Root:
    1. The transaction proof inputProof must be validated.
    2. The UTXO ID being produced by the output of inputProof must match proof.data[index].

File: src/v1.0.0/3. Concepts/2. Verifiers/Merkle Proof.md

Verifier: Merkle Proofs

Verifies the Merkle proof component of a transaction proof.

  1. The tree height proof.merkleProof.length must be less than MERKLE_TREE_HEIGHT_MAX (256).
  2. Hash transaction leaf proof.transaction to get current hash as the leaf hash.
  3. For height i between 0 and ``proof.merkleProof.length - 1`:
    1. Hash the current hash with its sibling from the Merkle proof. If the ith bit of proof.transactionIndex is 0, the sibling is on the right. If it's 1, the sibling is on the left.
  4. The resultant Merkle root must match proof.rootHeader.merkleTreeRoot.

Note: when constructing the tree, the number of nodes at a given level may not be even. In such case, the default value EMPTY_LEAF_HASH (0x0000000000000000000000000000000000000000000000000000000000000000) is used as the sibling hash value.


File: src/v1.0.0/3. Concepts/2. Verifiers/Transaction Proof.md

Verifier: Transaction Proofs

Validates a transaction proof is properly formatted.

  1. The header of the transaction proof must be valid.
  2. The Merkle proof of the transaction proof must be valid.
  3. The length of the transaction leaf transaction.length must be greater than 0.

TransactionProof

name type size description
blockHeader BlockHeader variable Block header substructure.
rootHeader RootHeader variable Root header substructure.
rootIndex uint16 2 Root index.
merkleProof.length uint16 2 Number of nodes in Merkle proof.
merkleProof bytes32[] 32* Merkle inclusion proof.
inputIndex uint8 1 Input index.
outputIndex uint8 1 Output index.
transactionIndex uint16 2 Transaction index.
transaction TransactionLeaf variable Transaction leaf substructure.
data.length uint8 1 Next field number of elements.
data bytes32[] 32* List of state elements being spent.
signatureFeeToken uint256 32 Implicit fee token ID.
signatureFee uint256 32 Implicit fee rate.
token address 20 Token address, used for invalid sum.
returnOwner address 20 For HTLC outputs only, the return owner.

File: src/v1.0.0/3. Concepts/2. Verifiers/Witness.md

Verifier: Witnesses

Validates the witness component of a transaction proof is properly formatted.

This is needed to ensure that the preimage to the transaction ID provided in the transaction proof proof (i.e. the unsigned transaction) is actually valid. Data is elided when being posting transactions to Ethereum, so the integrity of the data provided in the proof must be checked.

For the provided state element stateElement, witness = proof.transaction.witnesses[0], and input = proof.transaction.inputs[0]:

  1. If witness's type is Signature:
    1. Recover the address with signature witness and the transaction ID of computed from proof.transaction and proof.data[0] as message.
    2. If input's type is Transfer or HTLC:
      1. The recovered address must match stateElement.owner.
    3. If input's type is Deposit:
      1. The recovered address must match stateElement.owner.
    4. If input's type is Root:
      1. The recovered address must match proof.blockProducer.
  2. If witness's type is Caller:
    1. The witness registered indexed by stateElement.owner and stateElement.blockNumber must match the transaction ID of computed from proof.transaction and proof.data[0]
  3. If witness's type is Producer:
    1. The stateElement.hash must match the transaction ID of computed from proof.transaction and proof.data[0].

Note that stateElement can come in different types, depending on the type of the input (i.e. what is being spent).


File: src/v1.0.0/3. Concepts/3. Provers/Double Spend.md

Prover: Double Spends

Proves that a double spend occurred.

Two valid transaction proofs must be provided.

  1. The input metadata for each transaction proof must be different.
  2. The referenced input metadata (i.e. the spent state element) for each transaction proof must match.

File: src/v1.0.0/3. Concepts/3. Provers/Invalid Input.md

Prover: Invalid Inputs

Proves that an input was invalid, i.e. a non-existent state element was spent.

A valid transaction proof, proof, must be provided. In addition, a valid transaction proof block header for the output being spent, inputProof must be provided. Since this output might not exist, the transaction proof does not need to be valid, only the block header.

  1. Get input input as proof.transaction.inputs(proof.inputIndex).
  2. If the input's type is Deposit:
    1. The deposit with that deposit ID must have amount of 0 (i.e. a non-deposit is being spent).
  3. Get metadata metadata as proof.transaction.metadata[proof.inputIndex].
  4. The metadata's root index metadata.rootIndex must be at least inputProof.rootHeader.rootLength (i.e. metadata root index out of bounds).
  5. If the input's type is Transfer or HTLC:
    1. If the input proof is for the rightmost leaf in the root's transaction tree, then the metadata's transaction index metadata.transactionIndex must be greater than inputProof.transactionIndex. It's impossible to provide an inclusion proof for an out-of-bounds transaction, so this check serves as a check for this condition.
    2. The transaction leaf must be non-empty, i.e. inputProof.transaction.length must be greater than 0.
    3. The metadata's output index metadata.outputIndex must be at least inputProof.transaction.outputs.length() (i.e. metadata output index out of bounds).
    4. The output's type must be Withdraw or Return (i.e. spending a non-spendable output).
    5. If the input's type is Transfer:
      1. The output's type must not be Transfer (i.e. type mismatch).
    6. If the input's type is HTLC:
      1. The output's type must not be HTLC (i.e. type mismatch).
      2. If proof.blockHeader.blockNumber is less than inputProof.transaction.outputs(inputProof.outputIndex).expiry (i.e. the timelock has not expired):
        1. The hashlock digest inputProof.transaction.outputs(inputProof.outputIndex).digest must not match the hash of the preimage input.preImage (i.e. invalid preimage).

File: src/v1.0.0/3. Concepts/3. Provers/Invalid Sum.md

Prover: Invalid Sums

Proves that a transaction spent more than allowed (i.e. sum of outputs > sum of inputs + fees).

A valid transaction proof, must be provided. The proof's witness must also be valid. The proof's data must also be valid.

  1. For the provided token ID, the sum of the inputs must be greater than the sum of the outputs plus fees.
    1. Fees for a transaction are calculated as the feerate proof.fee times the transaction leaf length proof.transaction.length.

File: src/v1.0.0/3. Concepts/3. Provers/Invalid Transaction.md

Prover: Invalid Transactions

Proves that a transaction was malformed. This involves attempting to parse the transaction leaf.

A valid transaction proof, proof, must be provided.

  1. Get the transaction leaf component of the proof, leaf = proof.transaction.
  2. Metadata count leaf.metadata.length must be less than 0 or more than INPUTS_MAX.
  3. As a sanity check, witnesses length in bytes leaf.witnesses.length must be less than 0 or more than TRANSACTION_SIZE_MAX. Same for inputs length leaf.inputs.length and outputs length leaf.outputs.length.
  4. The leaf length leaf.length must be at less than TRANSACTION_SIZE_MIN or more than TRANSACTION_SIZE_MAX.
  5. The leaf total length leaf.length+2 (since the leaf.length field itself is 2 bytes) must not match that size of the leaf component of the proof.

The witnesses are parsed:

  1. For each witness, shift by the length of the witness:
    1. If the witness type is Caller:
      1. The transaction ID from the witness registry at the witness ID must be 0 (i.e. the witness has not bee registered).
      2. The block number of the witness must be at least the block number in proof (i.e. the witness was not registered strictly before the block was committed).
  2. The number of parsed witnesses must be greater than INPUTS_MAX.
  3. The parsed length of witnesses must not match leaf.witnesses.length.

The inputs are parsed:

  1. For each input, shift by the length of the input:
    1. The input type must be out of bounds.
    2. The witness reference index input.witnessIndex must be out of bounds (i.e. at least the number of witnesses).
  2. The number of parsed inputs must be greater than INPUTS_MAX.
  3. The parsed length of inputs must not match leaf.inputs.length.

The outputs are parsed:

  1. For each output, shift by the length of the output:
    1. The output type must be out of bounds.
    2. If the output type is Transfer, Withdraw, or HTLC:
      1. The output token and amount fields must be out of bounds.
      2. The output owner field must be out of bounds.
    3. If the output type is HTLC:
      1. the output return owner field must be out of bounds.
    4. If the output type is Return:
      1. The data length must be out of bounds.
  2. The number of parsed outputs must be greater than OUTPUTS_MAX.
  3. The parsed length of outputs must not match leaf.outputs.length.

The sizes are parsed:

  1. The number of metadata elements must not match the number of inputs.

The metadata are parsed:

  1. For each metadata, shift by the length of the metadata:
    1. The metadata fields must be out of bounds.
  2. The parsed length of metadata must not match leaf.metadata.length.

File: src/v1.0.0/3. Concepts/3. Provers/Invalid Witness.md

Prover: Invalid Witnesses

Proves that a witness was invalid, and that a spend wasn't authorized.

A transaction proof with valid inputs must be provided. In addition, a list of state elements (one for each input of the transaction) must be provided.

  1. For each input:
    1. The owner of the input must not match the owner of the corresponding state element.

File: src/v1.0.0/3. Concepts/3. Provers/Malformed Block.md

Prover: Malformed Blocks

Proves that a block was malformed. Given initial checks when committing a new block, the only case is when the Merkle root claimed in a root header is incorrectly computed.

  1. A valid block header and root header must be provided, along with the original list of transaction leaves committed to in the root header.
  2. Merkleizing the transaction leaves must not match the merkleTreeRoot field in the root header.

File: src/v1.0.0/3. Concepts/4. Additional Modules/Funnels.md

Token Funnels

The usual flow for using an ERC-20 token with a contract is to first approve an amount of tokens to transfer, then use transferFrom to actually transfer the tokens. In addition to poor UX, this has a number of issues:

  1. Approving with a non-zero approved balance allows an adversary to front-run the approval and essential double-transfer tokens.
  2. Users transferring tokens directly to a contract with transfer lose them, as the contract has no way of processing the transfer. This has resulted in millions of dollars being burned through user error.

We provide a token funnel, a contract that can accept direct transfers magically. Intuitively, the funnel contract's deterministic CREATE2 address itself is a commitment to a deposit to Fuel (generally, the hash of: the sender address, the token type, and the token amount). After transferring tokens to this address, the funnel contract can be deployed at any time to transfer to deposit properly to Fuel, then immediately self-destruct.

Readers familiar with Bitcoin might notice this bears many similarities to Pay-to-Script-Hash.

Specific implementation details can be found here.


File: src/v1.0.0/4. Tools and Applications/0. Fast Withdrawals.md

Fast Withdrawals

Withdrawals must normally wait finalizationDelay Ethereum blocks until the rollup block they are included in is finalized, at which point the funds can be withdrawn. As the finalization delay is supposed to be set quite long to be robust against censorship attacks and short-term chain congestion, this leads to poor UX. We can do better!

Fungible assets (ETH, ERC-20 tokens) can be withdrawn immediately with an atomic swap powered by HTLCs, which is one of the transaction output types Fuel supports.

Liquidity providers can offer up liquidity to users wanting to withdraw quickly, in exchange for a fee. Unlike lending protocols that are popular in DeFi, liquidity providers for fast withdrawals have zero systemic risk if they fully validate Fuel blocks before accepting a swap, as valid blocks are guaranteed to finalize and they can always catch invalid blocks.


File: src/v1.0.0/4. Tools and Applications/1. Token Minting.md

Mass Token Minting

Minting and distributing tokens to a large number of users is a fairly common pattern, especially for community-driven applications and rewards or reputation points. Generally, tokens of this form are distributed in a permissioned manner by a central party, as un-gameable global reputation is non-trivial. However, once the tokens have been distributed they cannot be reclaimed by the issuer.

While it is certainly possible to do this relatively inexpensively by using one-to-many transactions (as Fuel supports up to eight inputs and eight outputs per transactions), we can do even better!

First, let us consider how one would make distributing such tokens cheaper on Ethereum today. There are two easy, concrete methods:

  1. The issuer pre-signs an authorization to mint a certain number of tokens for a particular user. She then sends the user the pre-signed authorization, which the user can redeem to the token contract for tokens. In this case, the issuer does not pay anything to distribute the token, as gas costs are covered by the user.
  2. The issuer posts a commitment (e.g. a Merkle root) of new coin balances to the token contract, which is an O(1)-cost operation. Users can then withdraw their tokens with a Merkle inclusion proof. In this case, the issuer pays a constant cost to distribute tokens, regardless of the number of tokens or users.

These schemes do not work on Fuel v1, as generic smart contracts are not supported. But what if we combined some of the aspects of the two approaches?

Pre-signed transaction tree.

The scheme is actually quite simple:

  1. The issuer mints all the tokens at once on Ethereum, as a batch.
  2. The tokens are deposited to Fuel.
  3. The issuer now has a single UTXO (or rather, a deposit) with exactly the number of tokens they want to distribute.
  4. The issuer recursively pre-signs transactions that split this UTXO into eight outputs that are returned to the same address. This forms a tree of pre-signed transactions, as shown in the diagram above.
  5. Once the leaves are reached, each output is sent to the appropriate user's address. Users are then given the Merkle branch of pre-signed transactions to the transaction that would send them tokens.
  6. A user that wishes to redeem their tokens broadcasts the branch of pre-signed transactions to Fuel (or, more specifically, the transactions that haven't yet been included on Fuel from potential previous users that do the same), then spends the appropriate output with a new (potentially fee-paying) transaction.

Note: The issuer does not send the pre-signed transactions to Fuel. Indeed, the pre-signed transactions do not pay any fees and so would not be included barring out-of-bounds negotiation. Rather, the user pays fees for an entire branch of pre-signed transactions using Child-Pays-For-Parent (CPFP).

Readers familiar with Bitcoin's CTV (BIP-119) proposal should find this scheme very similar. However, rather than being enforced with on-chain rules, the commitment to a tree of transactions is simply an off-chain promise.

Note: The threat model for distributing tokens in this manner is not necessarily weaker than that of using a contract on Ethereum to enforce some rules. In this mass token minting scheme, the issuer could double-spend their pre-signed transactions. Notice however that the issuer in any case could simply just not issue the tokens in the first place, as distribution is permissioned, to the same effect.

Note: Equivalent functionality to CTV (BIP-119) is on the roadmap as a future addition with covenants.


File: src/v1.0.0/4. Tools and Applications/2. Subscriptions.md

Subscriptions

Recurring cancellable subscriptions can be accomplished through pre-signed transactions with HTLCs.

For a two-time recurring subscription, Alice can construct transactions as follows:

Subscription pre-signed transactions.

Each pre-signed transaction spends the HTLC UTXO of its parent after the timelock expires (which is set to the subscription period, e.g. 1 month for a monthly subscription). The transaction burns a portion of this UTXO (or otherwise pays for the subscription), and outputs the remainder to a new HTLC whose timelock is offset by another period. At any time, Alice can cancel the recurring payments by spending the HTLC UTXO with the hashlock condition, which is set to return the funds to an address she controls. Her wallet stores the preimage, or it can even be deterministically generated from her private key, e.g. by signing over the subscription identifier.

An additional output can be added to pay fees to the block producer, but that is an implementation detail.


File: src/v1.0.0/4. Tools and Applications/3. Non-Custodial Exchanges.md

Non-Custodial Exchanges

Exchange settlement on Fuel.

Non-custodial exchanges can be built by anyone on top of Fuel today. The order book is kept, and matching is done, off-chain, with settlement of each order happening on Fuel as a multi-user transaction.

In this example, Alice and Bob each consume one of their UTXOs in a transaction, which swaps the owner of each asset to produce two new UTXOs. (Fees and change are an implementation detail.) Note that since users sign over an entire transaction, there is no possibility of theft. Alice and Bob can each sign the transaction individually, and any time before the transaction is confirmed, can spend their UTXO in a different transaction to cancel the order.


File: src/v1.0.0/5. Future Roadmap/1. Planned Features.md

Roadmap

Fuel v0

Released as testnet at the beginning of 2020 (announcement).

Source code.

Features:

Fuel v1

Current release, as testnet.

Source code.

Features in addition to v0:

  • Optimize transaction format, reducing transaction sizes by ~50%
  • Contract refactoring in Yul+ for additional gas cost savings
  • Support HTLC and Return outputs
  • Extra module: Token funnel
  • Additional client-side applications:
    • mass token minting
    • liquidity provider for fast withdrawals
    • non-custodial exchange

Fuel v1.5

Future release, expected EOY 2020.

Features in addition to v1:

  • BLS signature aggregation (bringing TPS to 1,500-2,000)
  • Native NFT support
  • Optimistic mass migration

Fuel v2

Future released, expected 2021.

Features in addition to v1.5:

  • Predicate scripting
  • Covenants
  • Configurable state introspection (for Ethereum-style smart contracts)

name: Check Markdown links

on: push

jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create config file
run: |
echo "{ "replacementPatterns": [ { "pattern": "^/", "replacement": "/github/workspace/" } ] }" > config.json
- name: Check links
uses: gaurav-nelson/[email protected]
with:
config-file: config.json