Introduction

Note: This document is a work in progress. Its structure is not yet finalized, and significant changes may still be made to the content.

Fluent is the first blended execution network - an Ethereum L2 and framework that blends Wasm, EVM and (soon) SVM-based smart contracts into a unified execution environment.

Smart contracts from different VM targets can directly call each other on Fluent. Fluent is in public devnet and currently supports apps composed of Solidity, Vyper, and Rust contracts.

Glossary

IR (Intermediary Representation) — An intermediary representation of some bytecode, that is usually used to represent and optimized and execution-ready application state.

ZK (Zero Knowledge) — A cryptographic method where one party (the prover) can prove to another party (the verifier) that they know a value without conveying any information apart from the fact that they know the value.

STF (State Transition Function) — A function used to ensure the correctness of a system's state transition.

ISA (Instruction Set Architecture) — The part of the computer architecture related to programming, which includes the instruction set, word size, memory address modes, processor registers, and address and data formats.

AOT (Ahead Of Time) — A type of compilation that converts high-level code to a lower-level code or machine code before execution.

JIT (Just In Time) — A type of compilation that converts high-level code to intermediate or machine code at runtime, typically to enhance performance during execution.

EVM (Ethereum Virtual Machine) — The runtime environment for smart contracts in Ethereum, allowing code to be executed exactly as intended.

DA (Data Availability) — Refers to the accessibility and correctness of the data required to reconstruct the state of a blockchain.

Vision

While developing a zkVM for an EVM-compatible, Wasm based rollup on Ethereum, Fluent explored methods to optimize the proving process of smart contracts. A major challenge lies in the fact that using a zkVM to support multiple VMs only allows for the proof of the root STF, while the remaining nested execution of other VMs requires emulation.

To address this issue, Fluent proposes a VM capable of performing nested execution without incurring additional emulation overhead. This is achieved through a hardware acceleration process similar to the translation of smart contracts, applications, and precompiled contracts into a specialized low-level intermediary representation (IR) binary structure known as rWasm. This structure can be proven much more efficient compared to running emulator software.

With its unique interruption system, rWasm emerged as an innovative solution for handling nested calls and compiling diverse smart contracts, revolutionizing the landscape of efficient zk-Wasm based applications on Ethereum.

Meet Blended Execution

With blended execution on Fluent, developers can create applications using languages and tools from various VMs yet coexisting in the same execution space. This is possible because all smart contracts share the same execution environment. As a result, native composability is achieved for anything within Fluent that can be represented as rWasm (which relies on Wasm and LLVM, making 17+ languages available for development).

Different VMs can be supported using an AOT translation process (efficient but time-consuming) or an emulation process ( very fast integration but with additional overhead). Fluent can enable execution environments like the EVM, bringing Solidity and Vyper support. Similarly, the new language Sway from Fuel can be integrated by running an FVM execution precompile. Entire EEs can be seamlessly integrated and accommodated in various modes, offering either Isolated EE (complete compatibility with the original EE where interoperability is achieved using deposit/withdrawal process) or Blended EE ( improved native composability with other VMs on Fluent).

Both users and developers obtain major benefits from blended execution because it enhances the experience of developers who are ready to expand the Fluent ecosystem by bringing more development languages and execution environments on board.

Product Vision

The Fluent Blended Execution Layer will revolutionize the way developers create, deploy, and prove smart contracts by unifying multiple VMs into a single, highly efficient execution environment. At the heart of this vision is rWasm, a unique IR language designed to bridge the gap between various VMs and EEs such as the EVM, SVM, and Wasm. By eliminating the emulation overhead typically associated with nested execution, Fluent enables faster, more secure, and more scalable smart contract execution.

Key features:

  1. Unified Execution Environment: Support for multiple VMs and EEs through a single execution environment, enabling native composability between different smart contracts, tools, and languages.
  2. rWasm Integration: Full compatibility with standard Wasm and support for over 17+ traditional programming languages, allowing developers to use familiar tools such as Rust, C, Solidity, TinyGo, and AssemblyScript without sacrificing performance.
  3. Optimized Proving Infrastructure: Efficient proving through rWasm that drastically reduces overhead and complexity, providing optimal performance for ZK circuits.
  4. Seamless Expansion: Advanced AOT compilers to integrate new VMs and EEs, ensuring extensibility for future-proofing and scalability.
  5. Security and Extensibility: BlendedVM unifies the execution space, providing enhanced security and preventing vulnerabilities that arise from managing multiple VMs while enabling new VMs to be added in trusted or trustless modes.

The Fluent Blended Execution Layer empowers developers to build diverse applications without managing disparate systems, optimizing their experience and fostering rapid growth in the Ethereum ecosystem.

Technical Vision

Extracting traces is crucial for proving. It involves obtaining snapshots of stack and memory operations to feed into zk circuits. Aligning the IR language with the trace structure and ensuring its compatibility with zk is essential to minimize circuit size, ultimately improving proving speed and reducing code complexity. rWasm, as an IR binary language, combines execution concepts and maps its execution trace to the zkVM circuit structure. This approach aims to achieve optimal performance, minimizing both proving and execution overhead.

Blended execution natively supports multiple VMs and EEs within a single execution environment, and within Fluent, is enabled by employing a single IR known as rWasm, which serves as its primary VM. This IR enables the verification of all state transitions occurring within the system, encompassing various VMs and EEs. In the case of the Fluent L2, the EVM, SVM, and Wasm are supported. As a result, developers familiar with any of these primitives can leverage circuits designed for rWasm and effortlessly obtain the necessary optimal proving infrastructure for their applications. In essence, blended execution acts as a state verification function responsible for representing every operation within the Fluent execution layer.

Given that rWasm serves as the IR language for Fluent, it has the potential to represent not only Wasm or EVM but also other VMs and EEs. This is facilitated by providing dedicated AOT compilers for these platforms or in some cases utilizing emulation software.

rWasm is a derivative of the Wasm assembly language that keeps 100% backward compatibility with original Wasm standards. Leveraging the extensive adoption and support of Wasm, rWasm enables blockchain developers to effortlessly create new applications in traditional languages like Rust and C. Fluent's advanced AOT compilers handle all IR compilation tasks, simplifying the development and deployment process of Wasm-based blockchain applications in the Ethereum ecosystem.

Blended Execution

Blended Execution - is an approach that aims to increase the efficiency of executing smart contracts from diverse VMs by utilizing a unified IR known as rWasm. rWasm facilitates the expansion of system functionality by leveraging native composability across different execution environments. This innovative approach enables seamless execution of smart contracts, optimizing performance and enhancing the overall capabilities of the system. Blended Execution - is an approach to enhance the efficiency of executing smart contracts from various VMs through the use of a single IR known as rWasm, that helps to expand the functionality of the system by utilizing native composability across different execution environments.

This system allows for the verification of all state transitions within a unified execution environment, thereby minimizing emulation overhead typically associated with nested execution in various VMs. By employing rWasm, which retains full compatibility with standard Wasm, developers can seamlessly deploy and integrate smart contracts across multiple programming languages and execution environments. Fluent features native, real-time composability, as the different applications the network supports share the same execution space. The use of advanced AOT compilers allows for efficient integration of various VMs, such as the EVM and SVM, while maintaining optimal proving infrastructure.

Ultimately, blended execution serves as a state verification function, streamlining the development process and broadening the capabilities of builders on Ethereum the Fluent ecosystem without affecting developer experience.

BlendedVM vs MultiVM

MultiVM is an execution layer that provides multiple VMs for running user applications. It offers developers the opportunity to create new types of applications and combine different programming languages in a single platform. However, MultiVM requires developers to manage these VMs within the execution layer. Most of these VMs use varying ISAs (instruction set architectures), which impact execution costs and supported functionality. Developers must carefully handle these parameters to avoid miscalculations or vulnerabilities that could lead to malicious state trie modifications.

BlendedVM takes a similar approach to MultiVM, but instead of having multiple VMs, it uses a single VM to represent all VM operations. This is achieved by employing AOT compilers to translate user applications into native instructions for BlendedVM. This approach enhances security by preventing malicious state access outside the single VM, even in the event of a vulnerability in the AOT compiler. Furthermore, BlendedVM offers system extensibility, allowing developers to incorporate support for additional VMs or EEs in a trusted or trustless manner.

Architecture

Fluent is an Ethereum Layer 2 (L2) rollup designed to natively execute EVM, SVM and Wasm-based programs. Fluent exists as a unified state machine, where all contracts can call each other, regardless of which VM they were originally built for.

As a rollup, Fluent supports scalable and efficient execution by committing state changes to Ethereum L1. This process involves compressing the state changes using ZK proofs, specifically SNARKs.


The base architecture of Fluent

The Fluent operates on a modified version of Reth, using its own execution engine that replaces Revm. It maintains backward compatibility with most existing Ethereum standards, such as transaction and block structures. However, Fluent is not confined to Reth exclusively, as it features an independent execution runtime.

Furthermore, Fluent enables a fork-less runtime upgrade model by incorporating the most critical and upgradable runtime execution codebase within the genesis state. The only persistent element within the runtime is the transaction format.

Additionally, Fluent is always post-Cancun compatible and does not support any EIPs implemented before the Cancun fork. Maintaining backward compatibility with all previous forks is unnecessary. The EVM runtime can be upgraded to retain compatibility with EVM.

Execution Environment

The Fluent EE is designed to be universal, supporting various VMs and EEs. This universality is achieved through the rWasm VM, which executes and simulates different EEs. By using rWasm, Fluent translates all applications into a single execution language, enabling different EEs to share the same state trie. This shared state trie facilitates seamless interoperability among applications.

The Fluent EE achieving support for different EEs can vary significantly based on numerous factors. These factors include the type of cryptography used (particularly when a raw transaction is processed), the percentage of compatible features, and the address format.

There are two potential approaches to building an EE within Fluent:

  • Blended EE - native cross-EE interoperability (aka native composability)
  • Isolated EE - simulation for complete compatibility

Given that each EE/VM can introduce unique execution standards, various integration challenges may arise. Let's explore the most significant ones.

  1. Incompatible Cryptography: EEs can consume an entire transaction as an input, requiring the use of system bindings to verify signature correctness. Fluent supports pre-compiled contracts for a range of commonly used cryptographic functions (e.g., secp256k1, ed25519, bn254, bls384). Any missing cryptographic functions can be implemented as custom precompiled contracts.

  2. Different Address Format: Fluent uses an Ethereum-compatible 20-byte address format. If an address uses a different derivation format or has more bytes than a contract can store, there needs to be a way to handle this. Such addresses can be stored as so-called projected addresses, which are calculated using any hashing function. Account information can then be stored inside the storage mapping of the EE account.

  3. Varying Gas Calculation Policies: EEs/VMs may use different gas calculation policies compared to Fluent. To address this, Fluent has two distinct gas calculation policies:

    • Standard Gas Calculation: Applies to typical operations, assigning a specific gas cost to each rWasm opcode.
    • Manual Gas Calculation: Available exclusively for development purposes and for genesis precompiled smart contracts on Fluent.

    This approach ensures flexibility and maintains the integrity of operations across different environments.

  4. Custom Bytecode Storage: There is a need to store immutable data like custom bytecode or some EE specific context information. Fluent provides a preimage database that can be used to store immutable data, including custom bytecode or other data.

  5. Variable Storage Size: Some EEs implement variable storage key/value lengths. For instance, Solana uses 10kB chunks, while Cosmos utilizes variable key/value lengths with certain constraints. Storing these datasets within Ethereum's standard 32-byte storage format may lead to significant performance issues and increased gas consumption. To address this, Fluent offers an additional API that allows developers to use custom storage solutions for varying data lengths efficiently.

Blended EE

Blended EEs within Fluent operating as execution proxies. Since Fluent only supports rWasm bytecode, then every operation inside Fluent must be represented using rWasm ISA.


Architecture design of Blended EE

There are two main options for this approach:

  1. Emulation: Utilizing an embedded VM that operates as a Wasm-based smart contract. This method simplifies the process for developers, who can easily compile their VMs into rWASM. However, the overhead from emulation can occasionally lead to significant performance issues.

  2. Simulation: Employing an AOT compiler to convert the original bytecode into rWASM bytecode. This approach requires developers to support compilation from their IR to rWasm, which is a time-intensive process demanding rigorous testing. Despite the increased complexity in the deployment process, which involves running the entire compiler, the execution performance can be significantly enhanced.

For example, Fluent presently incorporates the EVM using the proxy contract method. A proxy with a delegate call forwards execution to a unique EVM loader smart contract. This setup eliminates the need for address mapping or transaction verification. ABI encoding/decoding format can be used, and contracts can be managed using default EVM-compatible data structures, such as storage, block/transaction structures.

Note: AOT compiler for EVM to rWasm is currently a work in progress and will eventually replace the existing proxy model.

Advantages:

  • Native Interoperability: Different EEs deployed with this approach coexist within the same state space and can natively interoperate with one another. Since they share the same address space, isolation isn't required.
  • Efficiency: Enables the inclusion of new VMs or EEs without stringent integration standards, enhancing overall efficiency.

Disadvantages

  • Limitations: Not all EEs can be integrated within this model due to inherent limitations and potential standard violations. This might necessitate the development of an additional migration toolkit, requiring developers to recompile or slightly modify applications deployed using the Isolated EE Simulation model.

For instance, CosmWasm can be integrated using both approaches. In an Isolated EE, the original address format can be maintained, while in a Fully Blended EE, native composability with other EEs on Fluent is achievable.

Composability

To achieve native composability on Fluent, an EE must coexist within the same trie space as the default EVM+Wasm EEs. It should adhere to established EVM standards, including address formatting and derivation strategies (for example, CREATE/CREATE2 for smart contract deployment). The EE must not execute arbitrary account or state modifications, and can only manage basic Ethereum-compatible accounts.

As a result, apps built with the proxy model can natively interoperate with other native-compatible EEs. Since they share the same address space, isolation isn't required. Consequently, Wasm apps can directly interact with EVM apps and vice versa.

Isolated EE

Achieving full EE compatibility with the original VM involves running an entire EE runtime as a smart contract. While this is the most straightforward approach to ensure compatibility with the original EE, it presents various challenges for integration and composability with other EEs on Fluent. These challenges can affect the management of storage, address formats, and gas calculations.


Architecture design of Isolated EE with an RPC adapter

To integrate an EE on Fluent using the isolated approach requires a Wasm-based smart contract with an entry point. This entry point takes a raw transaction as input, parses it, and then executes it.

fn main() {
    // get raw input transaction for your EE, parse and verify transaction
    let parsed_tx = parse_and_verify_tx_from_input();
    // execute transaction
    let exec_result = exec_tx(parsed_tx);
    // forward all required info if needed (like logs, output and exit codes)
}

Note: This snippet is pseudocode. For more detailed development guides, please refer to the Fluent Docs.

The biggest advantage of such an approach is that it can support custom transactions, signature verification mechanisms and even address formats. It works for EEs like the Solana and FuelVM.

Advantages:

  • High Compatibility: Full or almost full compatibility with the original EE.
  • Seamless Integrations: Utilizes compatible addresses and cryptography, maintains the same transaction format, enables the use of original tools, wallets, and explorers. Achieve full RPC support through a special RPC bridge or adapter.
  • Cost-Effective Development: Low development costs if the EE is no_std ready and can be compiled into Wasm.

Disadvantages:

  • Gas Management: Ensuring full compatibility poses a challenge due to gas management limitations. The gas measurement approach in Fluent EE often diverges from that in Isolated EE, making it generally impractical to align the two policies.
  • Interoperability Issues: Accounts in an isolated EE are emulated and cannot directly interact with contracts using the blended EE model (described below), requiring withdrawals. Despite an instant withdrawal process that can be executed through multicall, it increases execution costs and decreases user experience by needing to interact with separate wallets and token standards across isolated EEs.
  • Emulation Overhead: Certain functions introduce computation overhead due to discrepancies in state models or account and transaction structures. Running a VM inside a VM can cause performance losses.

Composability

By using this method, Isolated EEs maintain an original address format but lack native interoperability between the different EEs available on Fluent. To manage deposits and withdrawals from the EE, a special runtime contract must be utilized. This necessitates that funds be deposited into the EE via designated functions.

Fluent defines basic Solidity-ABI compatible interfaces to facilitate interaction with such EEs:

interface IAmFullyCompatibleButIsolatedEE {
    // Deposits ETH into the EE
    function depositETH(bytes32 address) external;
    
    // Deposits an ERC20 token into the EE
    function depositERC20(bytes32 address, IERC20 token) external;
    
    // Withdraws ETH from the EE
    function withdrawETH(bytes params) external;
    
    // Withdraws an ERC20 token from the EE
    function withdrawERC20(bytes params, IERC20 token) external;

    // Get non-claimed relayer fee    
    function getRelayerFee(address relayer) external view returns (u256);
    
    // Claim unclaimed relayer fee
    function claimRelayerFee() external;
}

Note: Method signatures are not fully finalized and may change in the future.

Contracts are expected to support Multicall, which allows developers to combine deposit and withdrawal operations into a chain of actions.

Blended VM

Blended VM implements Blended Execution concepts inside Fluent. It works on the top of rWasm VM, the runtime system bindings and interruption system. rWasm VM represents all state transitions within the VM, including Wasm instructions. The interruption system efficiently manages interruptions during system calls or cross-contract calls.

rWASM

rWasm (reduced WebAssembly) is an EIP-3540 compatible binary IR of Wasm. It is designed to simplify the execution process of Wasm binaries while maintaining 99% compatibility with original Wasm features.

rWasm is a specially modified binary IR of Wasm execution. It retains 99% compatibility with the original Wasm bytecode and instruction set but features a modified binary structure that avoids the pitfalls of non-zk friendly elements, without altering opcode behavior.

The main issue with Wasm is its use of relative offsets for type mappings, function mappings, and block/loop statements, which complicates the proving process. rWasm addresses this by adopting a more flattened binary structure without relative offsets and eliminating the need for a type mapping validator, allowing for straightforward execution.

The flattened structure of rWasm simplifies the process of proving the correctness of each opcode execution and places several verification steps in the hands of the developer. This modification makes rWasm a more efficient and zk-friendly option for integrating Web2 developers into the Web3 ecosystem.

Technology

rWasm is built on Wasmi's IR, originally developed by Parity Tech and now under Robin Freyler's ownership. Fluent implements the Wasmi VM because its IR is fully consistent with the original WebAssembly, ensuring compatibility and stability. For rWasm, Fluent adheres to the same principles, making no changes to Wasmi's IR and only modifying the binary representation to enhance ZK-friendliness.

Key Differences between rWasm and Wasm:

  • Deterministic Function Order: Functions are ordered based on their position in the codebase.
  • Block/Loop Replacement: Blocks and loops are replaced with Br-family instructions.
  • Redesigned Break Instructions: Break instructions now support program counter (PC) offsets instead of depth-level.
  • Simplified Binary Verification: Most sections are removed to streamline binary verification.
  • Unified Memory Segment Section: Implements all Wasm memory standards in one place.
  • Removed Global Variables Section: A global variables section is eliminated.
  • Eliminated Type Mapping: Type mapping is no longer necessary as the code is fully validated.
  • Special Entrypoint Function: A unique entry point function encompasses all segments.

The new binary representation ensures a fully equivalently compatible Wasmi runtime module from the binary. Some features are no longer supported by the rWasm runtime: module imports; global variables; memory imports; global variables export. These features are unnecessary as Fluent does not utilize them.

Structure

The rWasm binary format supports the following sections:

  • Bytecode Section: Replaces the function/code/entrypoint sections.
  • Memory Section: Replaces memory/data sections for all active/passive/declare section types.

The following sections, currently implemented, are scheduled for removal:

  • Function Section: This section determines the size of each function and is used to properly allocate functions in memory. Once the CallInternal opcode is eliminated, this section can be dropped as well.
  • Element Section: This section defines allocations for tables used in indirect calls. The CallIndirect instruction is being phased out, and once this process is complete, the section will also be removed.

Bytecode Section

This section consolidates Wasm's original function, code, and start sections. It contains all instructions for the entire binary without any additional separators for functions. Functions are recovered from the bytecode by reading the function section, which contains function lengths. The entrypoint function is injected at the end, which is used to initialize all segments according to Wasm constraints.

Note: The function section is planned to be removed and entrypoint stored at offset 0. To achieve this, eliminating stack calls must be achieved while implementing indirect breaks. Although Fluent has an implementation for this, it is not yet satisfactory, and a migration is planned to a register-based IR before finalizing it.

Memory & Data Section

In Wasm, memory and data sections are handled separately. In rWasm, the Memory section defines memory bounds (lower and upper limits), and data sections, which can be either active or passive, and specify data to be mapped inside memory. Unlike Wasm, rWasm eliminates the separate memory section, modifies the corresponding instruction logic, and merges all data sections.

Here's an example of a WAT file that initializes memory with minimum and maximum memory bounds (default allocated memory is one page, and the maximum possible allocated pages are two):

(module
  (memory 1 2)
)

To support this, the memory.grow instruction is injected into the entrypoint to initialize the default memory. A special preamble is also added to all memory.grow instructions to perform upper bound checks.

Here is an example of the resulting entrypoint injection:

(module
  (func $__entrypoint
    i32.const $_init_pages
    memory.init
    drop)
)

According to Wasm standards, a memory overflow causes u32::MAX to be placed on the stack. For upper-bound checks, the memory.size opcode can be used. Here is an example of such an injection:

(module
  (func $_func_uses_memory_grow
    (block
      local.get 1
      memory.size
      i32.add
      i32.const $_max_pages
      i32.gts
      drop
      i32.const 4294967295
      br 0
      memory.grow)
  )
)

These injections fully comply with Wasm standards, allowing Fluent to support official Wasm memory constraint checks for the memory section.

For the data section, the process is more complex because Fluent needs to support three different data section types:

  • Active: Has a pre-defined compile-time offset.
  • Passive: Can be initialized dynamically at runtime.

To address this, all sections are merged. If the memory is active, it is initialized inside the entrypoint with re-mapped offsets. Otherwise, the offset is remembered in a special mapping to adjust passive segments when the user calls memory.init manually.

Here is an example of an entrypoint injection for an active data segment:

(module
  (func $__entrypoint
    i32.const $_relative_offset
    i64.const $_data_offset
    i64.const $_data_length // or u64::MAX in case of overflow
    memory.init 0
    data.drop $segment_index+1
  )
)

The data segment must be dropped finally. According to Wasm standards, once the segment is initialized, it must be entirely removed from memory. To simulate this behavior, Fluent uses zero segments as a default and stores special data segment flags to know which segments are still active.

For passive data segments, the logic is similar, but data segment offsets must be recalculated on the fly.

(module
  (func $_func_uses_memory_init
    // adjust length
    (block
      local.get 1
      local.get 3
      i32.add
      i32.const $_data_len
      i32.gts
      br_if_eqz 0
      i32.const 4294967295 // an error
      local.set 1
    )
    // adjust offset
    i32.const $_data_offset
    local.get 3
    i32.add
    local.set 2
    // do init
    memory.init $_segment_index+1
  )
)

The provided injections are examples and may vary based on specific requirements.

Interruptions

Fluent interoperability relies heavily on its interruption system. Smart contracts on Fluent are limited to pure functions, preventing system calls from accessing external resources such as bytecodes, cold or invalidated storage slots, or performing nested calls. Including system calls imposes additional proving overhead, as proving gadgets must be developed for each call, complicating system development. This also impacts the flexibility in managing rights or extending contracts, making the system less sustainable for the fork-less concept. In such a scenario, system contracts cannot be upgraded without updating the circuits.

Fluent solves this problem by enabling an interruption system that helps manage context switching between nested apps and the so-called STF that stands for context management.

For simplicity, let's assume that STF, smart contracts and EEs are all functions (since they are essentially state transition functions or a part of STF). Functions can be categorized as either root or non-root. A root function is defined as a function where the depth level is equal to 0. The root function is pivotal because it handles all context switching and cross-contract accesses, serving as a security layer.

The root function has the ultimate authority over all state transitions within the blockchain. Additionally, the root function is in charge of managing and executing system calls. The root function cannot be interrupted, but it is capable of handling interruptions.

System Bindings

The system bindings manage context switching in a Fluent interruption system. The following functions are provided:

  1. _exec(..) - execute bytecode
  2. _resume(..) - resume interrupted state
  3. _exit(..) - exit contract

Application Exit

The application exit binding terminates function execution with the specified exit code. The function is designed to exit from any smart contract or application. It immediately halts the contract execution and forwards all execution results, including the exit code and return data, to the caller contract.

#![allow(unused)]
fn main() {
pub fn _exit(code: i32) -> !;
}

Constraints: The exit code must always be a negative 32-bit integer. Supplying a positive exit code will result in a NonNegativeExitCode execution error. Positive exit codes indicate interrupted execution and are exclusive to the _exec or _resume functions.

img.png

title Application Exit Flow

activate root-STF
root-STF->SmartContract:call smart contract
activate SmartContract
SmartContract-->root-STF:exit with code
deactivate SmartContract
deactivate root-STF

Execute Bytecode or Send Interruption

This binding executes a nested call with the specified bytecode poseidon hash, or sends an interruption to the parent execution call though context switching. If the depth level is greater than 0 then an interruption occurs, otherwise, bytecode is executed.

Parameters:

  • hash32_ptr: A pointer to a 254-bit poseidon hash of a contract to be called.
  • input_ptr: A pointer to the input data (const u8).
  • input_len: The length of the input data (u32).
  • fuel_ptr: A mutable pointer to a fuel value (u64). The consumed fuel is stored in the same pointer after execution.
  • state: A state value (u32), used internally to maintain function state.

Returns:

  • An i32 value indicating the result of the execution. A negative or zero result stands for terminated execution, while a positive code stands for interrupted execution (works only for root execution level).
#![allow(unused)]
fn main() {
pub fn _exec(
    hash32_ptr: *const u8,
    input_ptr: *const u8,
    input_len: u32,
    fuel_ptr: *mut u64,
    state: u32,
) -> i32;
}

img.png

title Application Exec/Resume Flow

activate root-STF
root-STF->A:call contract A\nusing _exec() func
activate A
A->A:call contract B using\n_exec() func
A-->root-STF:interrupt execution\nwith saved context
deactivate A
activate B
root-STF->B: call contract B using _exec() func
B->B: call _exit func to\nhalt execution
B-->root-STF: halt with exit code
deactivate B
root-STF->A: resume A call\nusing _resume() func
activate A
A->A: call _exit func to\nhalt execution
A-->root-STF: exit with exit code
deactivate A
deactivate root-STF

Resume Execution

Resumes the execution of a previously suspended function call.

Parameters:

  • call_id: A unique identifier for the call that needs to be resumed.
  • return_data_ptr: A pointer to the return data that needs to be passed back to the resuming function. This should point to a byte array.
  • return_data_len: The length of the return data in bytes.
  • exit_code: An integer code that represents the exit status of the resuming function. Typically, this might be 0 for success or an error code for failure.
  • fuel_ptr: A mutable pointer to a 64-bit unsigned integer representing the fuel needed to be charged. The consumed fuel result is also stored in the same pointer.

Returns:

  • An i32 value indicating the result of the resumption.
#![allow(unused)]
fn main() {
pub fn _resume(
    call_id: u32,
    return_data_ptr: *const u8,
    return_data_len: u32,
    exit_code: i32,
    fuel_ptr: *mut u64,
) -> i32;
}

The resume function operates similarly to exec, but it requires an interrupted call ID and the interruption result (including return data and exit code). Interruption events may also occur during the resume process, requiring an execution loop capable of handling and correctly processing these interruptions.

System Calls

System calls use the same approach as an interruption system. Since root-STF function is responsible for all state transitions, including cold/warm storage reads, then a syscall can be represented as an interruption to the ephemeral smart contract.

For accessing state data, Fluent uses special ephemeral smart contracts to access information located outside a smart contract. For example, in case of storage cache invalidation, the contract must request the newest info from the root call instead of reading invalidated cache. Also, nested calls to other contracts require ACL checks that must be checked and verified by the root-STF.

Here is an example of what the system call looks like for Rust contracts.

#![allow(unused)]
fn main() {
fn syscall_storage_read<SDK: NativeAPI>(native_sdk: &mut SDK, slot: &U256) -> U256 {
  // do a call to the root-STF to request some storage slot
  let (_, exit_code) = native_sdk.exec(
      &SYSCALL_ID_STORAGE_READ, // an unique storage read code hash
      slot.as_le_slice(), // a requesting slice with data (aka call-input)
      GAS_LIMIT_SYSCALL_STORAGE_READ, // a gas limit for this call (max threshold)
      STATE_MAIN, // state of the call (must always be 0, except some special tricky cases)
  );
  // make sure returning result is zero (Ok)
  assert_eq!(exit_code, 0);
  // read output from the return data (storage slot value is always 32 bytes)
  let mut output: [u8; 32] = [0u8; 32];
  native_sdk.read_output(&mut output, 0);
  // convert return data to the U256 value
  U256::from_le_bytes(output)
}
}

For example, if smart contract A needs to send a message to smart contract B, it can trigger a special system call interruption. Upon interruption, the root-STF (State Transition Function) processes the interruption, performs ACL (Access Control List) checks, executes the target application, and then resumes the previous context with the appropriate exit code and return data.

img.png

title Storage Write/Read Syscall

activate root-STF
root-STF->A:call contract A\nusing _exec() func
activate A
A->A:call storage write/read\nusing _exec() func
A-->root-STF:interrupt execution\nwith the saved context
deactivate A
activate root-STF
root-STF->root-STF: request the slot specified\nfrom database
root-STF->A: resume A call using _resume() func\nwith storage write/read return data
deactivate root-STF
activate A
A-->root-STF: exit with exit code
deactivate A
deactivate root-STF

EVM

Fluent integrates with the EVM by leveraging special EVM precompiled contracts. These contracts facilitate the execution of EVM bytecode, enabling the deployment and operation of smart contracts designed for the EVM ecosystem. This allows developers to seamlessly deploy their applications built for EVM platforms using languages like Solidity or Viper.

The EVM executor, a Rust-based smart contract, provides two key functions:

  1. deploy: Deploys the EVM application and stores the bytecode state.
  2. main: Executes the already deployed EVM application.

During deployment, a specialized rWasm proxy is deployed under the smart contract address. This proxy redirects all deployment and execution calls to the EVM executor.

The deployment process is identical to that of Ethereum and other Ethereum-compatible platforms. Additionally, there are no differences in calling conventions or contract interactions. This consistency ensures a smooth app migration process for developers.

Note: EVM proxy mode is currently under testing and may be disabled in your environment until testing is complete, but it doesn't affect EVM-compatability or developer experience anyhow.

Note: There is an AOT compiler that translates EVM into rWasm directly, but it's still under development and testing.

The diagram below describes deployment and invocation flow for EVM applications.

Untitled.svg

title EVM deployment/call flow

activate User
User->User: compile solidity\napplication and sign\ntransaction
User->Fluent: send signed\ntransaction
activate Fluent
Fluent->Fluent: save proxy into the\ndeployed account
Fluent->Proxy: call `deploy` stage
activate Proxy
Proxy->EVM: delegate call into\nthe EVM executor
EVM->EVM: execute EVM bytecode and\nstore result in the state
EVM-->Proxy: return deployment result
Proxy-->Fluent: forward deployment result
deactivate Proxy
deactivate Fluent
Fluent-->User: return deployment result

User->Fluent: call just deployed contract
activate Fluent
Fluent->Proxy: call `main` stage
activate Proxy
Proxy->EVM: delegate call into\nthe EVM executor
EVM->EVM: execute EVM bytecode and\nstore result in the state
EVM-->Proxy: return deployment result
Proxy-->Fluent: forward deployment result
deactivate Proxy
Fluent-->User: return deployment result
deactivate Fluent
deactivate User

WebAssembly

Fluent offers near-native support for Wasm, with the primary distinction being that, during deployment, it's compiled into rWasm. A Wasm application can use the same system calls as EVM applications without any restrictions.

During the deployment process, Fluent enhances the rWasm codebase with additional checks for gas measurement and modifies certain instructions or segment structures when necessary. For more details, refer to the rWasm section.

Solana Integration

Solana natively supports composability with both EVM and Wasm applications. This is made possible because Fluent addresses Solana applications by mapping them into Fluent's account space. To achieve SVM support, a special rPBF executor is employed, which defines the execution of Solana binaries and specifies a list of mapped system bindings and calls. Native support for rPBF bytecode is achieved by mapping each operation into the Fluent EE space.

Address Format

Solana uses a 32-byte address format, while Fluent operates with a 20-byte format. Instead of storing a mapping from the 32-byte to the 20-byte format, Fluent employs a special address convention to convert addresses between formats. A unique magic prefix is attached to Solana addresses to make them convertible into the same binary representation within the 20-byte Fluent account trie.

The first 12 bytes of the address are used to route transfers and calls between SVM and EVM+Wasm accounts. This routing is necessary to achieve full EE compatibility and perform additional containment checks when required. For example, while a simple transfer without invoking callee bytecode is not allowed in EVM, it is possible in SVM. Therefore, differentiating between SVM and EVM accounts is essential.

Transaction Type

Fluent does not support Solana transactions directly; instead, EIP-1559 transactions must be used. During deployment, Fluent automatically detects Solana applications (EFL+rPBF) and specifies a special proxy that refers to the SVM executor system's precompiled contract.

Currently, Fluent does not support an additional transaction type for Solana transactions. However, this feature could be added in the future if there is enough demand.

PDA (Program-Derivable Address)

Program Derived Addresses (PDA) are a core feature of Solana, allowing for the management of nested contracts with dedicated storage. Fluent leverages various derivation schemes for Solana programs, ensuring that the migration process remains seamless for developers. The CREATE2 derivation scheme is used to replicate the PDA functionality.

To maintain an identical storage layout, Fluent offers supplementary storage interfaces. These interfaces enable Solana applications to efficiently manage data chunks without incurring extra overhead.

Accounts

Fluent employs account projection, also known as EE simulation, to integrate Solana accounts into the unified Fluent account space. This approach ensures that all EVM, Wasm, and SVM accounts are treated uniformly, leveraging the same rWasm VM. Consequently, this enables seamless interoperability and balance transfers between various account types.

Native Precompiled Contracts

Fluent allows extending any contract with additional functionality through precompiled WASM modules. For example, by implementing a multicall precompiled contract, any contract can process batched transactions when called with the multicall selector (0xac9650d8). This system-wide extension mechanism doesn't require modifying the original contracts.

Architecture

The implementation is based on function selector matching during bytecode execution. When the BlendedRuntime executes bytecode, it checks the first four bytes of the input data (function selector) against known precompiled contract selectors. If there's a match, execution is redirected to the corresponding precompiled contract.

Precompile Address

Each precompiled contract has a deterministic address generated from: keccak256("precompile")[..16] + function_selector[..4]

Available Precompiles

Multicall

Multicall enables batching multiple calls into a single transaction for any contract in the system. The implementation is compatible with OpenZeppelin's Multicall.

The key feature is that multicall works with any contract address—the system automatically detects the multicall selector and routes the call through the precompiled contract while preserving the original contract's context.

Interface

#![allow(unused)]
fn main() {
#[function_id("multicall(bytes[])")] // 0xac9650d8 
pub fn multicall(&mut self, data: Vec<Bytes>) -> Vec<Bytes> {}
}

Parameters:

  • data: Array of encoded function calls to be executed
  • results: Array of return data from each call

How It Works

When a call with the Multicall selector (0xac9650d8) is made to any contract:

  1. System detects the selector in the first four bytes of input data
  2. Redirects execution to the Multicall precompile
  3. Uses delegate_call for each batched call to preserve the original contract's context
  4. Reverts the entire transaction if any call fails
  5. Returns results from all calls on success

System Builtins

A collection of system functions provides access to low-level operations, serving both our VM runtime and our circuit definitions. Each system function is replaced with a specialized ZK-gadget to speed up the proving process. These functions can include hashing algorithms, I/O operations, and nested call functions.

WARNING: The system functions API/ABI are still under development and may change in the future.

#![allow(unused)]
fn main() {
#[link(wasm_import_module = "fluentbase_v1preview")]
extern "C" {
    /// Functions that provide access to crypto elements, right now we support following:
    /// - Keccak256
    /// - Poseidon (two modes, message hash and two elements hash)
    /// - Ecrecover
    pub fn _keccak256(data_offset: *const u8, data_len: u32, output32_offset: *mut u8);
    pub fn _poseidon(data_offset: *const u8, data_len: u32, output32_offset: *mut u8);
    pub fn _poseidon_hash(
        fa32_offset: *const u8,
        fb32_offset: *const u8,
        fd32_offset: *const u8,
        output32_offset: *mut u8,
    );
    pub fn _ecrecover(
        digest32_offset: *const u8,
        sig64_offset: *const u8,
        output65_offset: *mut u8,
        rec_id: u32,
    );

    /// Basic system methods that are available for every app (shared and sovereign)
    pub fn _exit(code: i32) -> !;
    pub fn _write(offset: *const u8, length: u32);
    pub fn _input_size() -> u32;
    pub fn _read(target: *mut u8, offset: u32, length: u32);
    pub fn _output_size() -> u32;
    pub fn _read_output(target: *mut u8, offset: u32, length: u32);
    pub fn _forward_output(offset: u32, len: u32);
    pub fn _state() -> u32;
    pub fn _read_context(target_ptr: *mut u8, offset: u32, length: u32);

    /// Executes a nested call with specified bytecode poseidon hash.
    ///
    /// # Parameters
    /// - `hash32_ptr`: A pointer to a 254-bit poseidon hash of a contract to be called.
    /// - `input_ptr`: A pointer to the input data (const u8).
    /// - `input_len`: The length of the input data (u32).
    /// - `fuel_ptr`: A mutable pointer to a fuel value (u64), consumed fuel is stored in the same
    ///   pointer after execution.
    /// - `state`: A state value (u32), used internally to maintain function state.
    ///
    /// Fuel ptr can be set to zero if you want to delegate all remaining gas.
    /// In this case sender won't get consumed gas result.
    ///
    /// # Returns
    /// - An `i32` value indicating the result of the execution,
    /// negative or zero result stands for terminated execution,
    /// but positive code stands for interrupted execution (works only for root execution level)
    pub fn _exec(
        hash32_ptr: *const u8,
        input_ptr: *const u8,
        input_len: u32,
        fuel_ptr: *mut u64,
        state: u32,
    ) -> i32;

    /// Resumes the execution of a previously suspended function call.
    ///
    /// This function is designed to handle the resumption of a function call
    /// that was previously paused.
    /// It takes several parameters that provide
    /// the necessary context and data for resuming the call.
    ///
    /// # Parameters
    ///
    /// * `call_id` - A unique identifier for the call that needs to be resumed.
    /// * `return_data_ptr` - A pointer to the return data that needs to be passed back to the
    ///   resuming function.
    /// This should point to a byte array.
    /// * `return_data_len` - The length of the return data in bytes.
    /// * `exit_code` - An integer code that represents the exit status of the resuming function.
    ///   Typically, this might be 0 for success or an error code for failure.
    /// * `fuel_ptr` - A mutable pointer to a 64-bit unsigned integer representing the fuel need to
    ///   be charged, also it puts a consumed fuel result into the same pointer
    pub fn _resume(
        call_id: u32,
        return_data_ptr: *const u8,
        return_data_len: u32,
        exit_code: i32,
        fuel_ptr: *mut u64,
    ) -> i32;

    pub fn _charge_fuel(delta: u64) -> u64;
    pub fn _fuel() -> u64;

    /// Journaled ZK Trie methods to work with blockchain state
    pub fn _preimage_size(hash32_ptr: *const u8) -> u32;
    pub fn _preimage_copy(hash32_ptr: *const u8, preimage_ptr: *mut u8);

    pub fn _debug_log(msg_ptr: *const u8, msg_len: u32);
}
}

For each system function, a unique identifier is assigned. During the rWASM translation, every function call is replaced with a Call(SysCallIdx) instruction. This approach significantly enhances the efficiency and simplicity of the proving process.

#![allow(unused)]
fn main() {
#[repr(u32)]
#[allow(non_camel_case_types)]
pub enum SysFuncIdx {
    #[default]
    UNKNOWN = 0x0000,

    // crypto
    KECCAK256 = 0x0101,
    POSEIDON = 0x0102,
    POSEIDON_HASH = 0x0103,
    ECRECOVER = 0x0104,

    // SYS host
    EXIT = 0x0001,
    STATE = 0x0002,
    READ = 0x0003,
    INPUT_SIZE = 0x0004,
    WRITE = 0x0005,
    OUTPUT_SIZE = 0x0006,
    READ_OUTPUT = 0x0007,
    EXEC = 0x0009,
    RESUME = 0x000a,
    FORWARD_OUTPUT = 0x000b,
    CHARGE_FUEL = 0x000c,
    FUEL = 0x000d,
    READ_CONTEXT = 0x000e,

    // preimage
    PREIMAGE_SIZE = 0x070D,
    PREIMAGE_COPY = 0x070E,

    DEBUG_LOG = 0x0901,
}
}

ABI

In the realm of EVM applications, the Solidity ABI has become the predominant encoding format, effectively establishing itself as a primary standard for on-chain interactions today. This encoding and decoding schema, primarily driven by the Solidity language, is widely used across Ethereum and other EVM-compatible platforms.

However, in the Web2 domain, developers opt for ABI encoding/decoding schemes that best fit their specific needs and tasks. Notably, Ethereum includes several system precompiles that do not conform to a Solidity-compatible ABI schema.

Blended VM Fluent distinguishes itself by supporting a variety of execution environments, such as EVM and Solana's VM, using distinct ABI schemes tailored to each environment. In Solana, for instance, there isn't a standardized ABI format, granting developers the flexibility to choose any format that suits their requirements.

This flexibility is a hallmark of Fluent, as it does not mandate a single encoding/decoding standard for applications. Instead, it empowers developers to select the most suitable option. The Fluentbase SDK accommodates this by implementing the necessary ABI encoding/decoding standards, allowing developers to freely use any ABI format they prefer.

Fluentbase Codec

Fluent employs a custom codec for ABI encoding/decoding tailored to various ABIs. This Codec includes compatibility modes such as Solidity ABI, and is designed to efficiently encode and decode parameters across different VMs.

Fluent Codec is a lightweight library, compatible with no-std environments, and optimized for random reads. Although it shares similarities with Solidity ABI encoding, it incorporates several optimizations and features to enhance efficient data access and handle nested structures more effectively.

The Codec leverages a header/body encoding mode:

  • Header: Contains all static information.
  • Body: Contains all dynamic information.

Key Features:

  • No-std Compatible: Operates in environments without the standard library.
  • Configurable Byte Order and Alignment: Allows customization according to requirements.
  • Solidity ABI Compatibility Mode: Seamlessly integrates with Solidity ABI.
  • Random Access: Accesses first-level information without requiring full decoding.
  • Support for Nested Structures: Encodes nested structures recursively.
  • Derive Macro Support: Facilitates custom type encoding/decoding via derive macros.

Encoding Modes

The library supports two primary encoding modes:

  • SolidityABI: Applied for external cross-contract calls to handle input parameters and decode outputs effectively.
  • FluentABI: Utilized for internal cross-system calls, benefiting from 4-byte stack alignment for efficiency without compromising the developer experience.

SolidityABI Mode

Parameters:

  • Big-endian byte order
  • 32-byte alignment (Solidity compatible)
  • Dynamic structure encoding:
    • Header
      • offset (u256) - a position in the structure
    • Body
      • length (u256) - a number of elements
      • recursively encoded elements

Usage example:

#![allow(unused)]
fn main() {
use fluentbase_codec::SolidityABI;
SolidityABI::encode(&value, &mut buf, 0)
}

FluentABI Mode

Parameters:

  • Little-endian byte order
  • 4-byte alignment
  • Dynamic structure encoding:
    • Header
      • length (u32) - a total number of elements in the dynamic data
      • offset (u32) - a position in the buffer
      • size (u32) - a total number of encoded elements bytes
    • Body
      • recursively encoded elements

Usage example:

#![allow(unused)]
fn main() {
use fluentbase_codec::FluentABI;
FluentABI::encode(&value, &mut buf, 0)
}

Type System

Primitive Types

Primitive types are encoded directly without any additional metadata, offering zero-cost encoding when their alignment matches the stack size.

TODO: add all types

  • Integer types: u8, i8, u16, i16, u32, i32, u64, i64
  • Static arrays: [T; N]

Non-Primitive Types

These types require additional metadata for encoding:

  • Vec<T>: Dynamic array of encodable elements
  • HashMap<K,V>: Hash map with encodable keys and values
  • HashSet<T>: Hash set with encodable elements

For dynamic types, the codec stores metadata that enables partial reading. For example:

  • Vectors store offset and length information
  • HashMaps store separate metadata for keys and values, allowing independent access

Important Notes

Determinism

The encoded binary is not deterministic and should only be used for parameter passing. The encoding order of non-primitive fields affects the data layout after the header, though decoding will produce the same result regardless of encoding order.

Order Sensitivity

The order of encoding operations is significant, especially for non-primitive types, as it affects the final binary layout. For non-ordered set/map data structures, ordering by key is applied.

Usage Examples

Basic Structure

#![allow(unused)]
fn main() {
use fluentbase_codec::{Codec, FluentABI};
use bytes::BytesMut;

#[derive(Codec)]
struct Point {
    x: u32,
    y: u32,
}

// Encoding
let point = Point { x: 10, y: 20 };
let mut buf = BytesMut::new();
FluentABI::encode(&point, &mut buf, 0).unwrap();

// Decoding
let decoded: Point = FluentABI::decode(&buf, 0).unwrap();
}

Dynamic Array Example

#![allow(unused)]
fn main() {
// Vector encoding with metadata
let numbers = vec![1, 2, 3];

// FluentABI encoding (with full metadata)
let mut fluent_buf = BytesMut::new();
FluentABI::encode(&numbers, &mut fluent_buf, 0).unwrap();
// Format: [length:3][offset:12][size:12][1][2][3]

// SolidityABI encoding
let mut solidity_buf = BytesMut::new();
SolidityABI::encode(&numbers, &mut solidity_buf, 0).unwrap();
// Format: [offset:32][length:3][1][2][3]
}

Account Trie

The account structure in rWASM is designed to be as simple as possible, containing only the most essential fields. It's fully compatible with an original Ethereum account structure.

#![allow(unused)]
fn main() {
pub struct Account {
    pub address: Address,
    pub balance: U256,
    pub nonce: u64,
    pub code_hash: B256,
    pub code_size: u64,
}
}

Fields description:

  • address: This transient field holds the account address. Currently, a 20-byte address is used to ensure compatibility with the EVM account structure. However, there is a possibility of extending this to 32 bytes in the future to align with the state trie account path, thereby enhancing interoperability.
  • balance: Represents the account balance as a 256-bit element. This is consistently expressed as a 256-bit Big Endian value, although future changes may be considered.
  • nonce: Indicates the number of transactions initiated by this account. It increments with each transaction, call, or contract creation, regardless of the operation's success.
  • code_hash: A Poseidon hash representing the translated bytecode in rWASM format.
  • code_size: Denotes the size of the compiled bytecode in rWASM IR binary format. This bytecode has successfully passed all static validations and is optimized for ZK proofs.

State Trie

The trie structure uses SMBT (Sparse Merkle Binary Trie), a fork of ZkTrie originally developed and maintained by Scroll.

Note: Considering replacing the trie hashing function with a more efficient alternative.

Fluent operates with state tries through a pure functional approach, where every smart contract and root-STF can be represented as a function. In this model, the input provides a list of dependencies, and the output yields an execution result along with a number of logs.

However, this isn't entirely feasible due to cold storage reads and external storage dependencies, such as CODEHASH-like EVM opcodes. To address this, Fluent employs an interruption system to "request" missing information from the root-STF. This is particularly useful for operations involving cold storage or invalidated warm storage.

The same concept is also used to handle nested calls without incurring additional simulation overhead.

Account Destruction Problem

The Ethereum Virtual Machine (EVM) utilizes dirty storage and journals to roll back changes and revert to previous states. One of the main challenges is handling the SELFDESTRUCT opcode, which is responsible for account destruction.

Account destruction can introduce several edge cases. When an account is destructed, all state changes, including storage modifications, must be reverted, irrespective of the destruction stage.

In the post-CANCUN era, Fluent has no need to remove existing storage tries. Account destruction in Fluent can be accomplished by sending a special interruption message into the root-STF with a specified command. Since Fluent-STF is a pure function, it can implement account destruction through journal reversion.

Genesis

The Genesis block, numbered 0, serves as the initial block in the blockchain. It defines fundamental blockchain parameters, the genesis hash, and stores the initial blockchain state, including system contracts.

Fluent Precompiled Contracts

Fluent provides the following genesis contracts:

  • EVM: 0x0000000000000000000000000000000000005210
  • WASM: 0x0000000000000000000000000000000000005220
  • SVM: 0x0000000000000000000000000000000000005230

EVM

EVM precompiled contract is responsible for managing EVM deployment and execution. It embeds EVM virtual machine with ISA.

The contract defines two methods:

  • deploy - for deploying EVM smart contract using EVM constructor (aka init code)
  • main - for executing already deployed EVM contract

Keccak256 compatible code hash is stored inside special constant storage slot calculated using function keccak256("_evm_bytecode_hash"). The EVM bytecode is stored in the special preimage storage that uses CREATE2 for storing custom immutable data.

  • EVM_BYTECODE_HASH_SLOT: 0xfd8a2cf66e0f80fe20ebc0e96c0e08e69c883c792a0409d4f4f92413fb66e980

WASM

The contract is disabled for now, WASM support is achieved natively

SVM

TBD

EVM-compatible Precompiled Contracts

EVM precompiled contracts are fully compatible with the original EVM contracts and are maintained with the following addresses:

  • SECP256K1_ECRECOVER (0x0000000000000000000000000000000000000001): Used for recovering the public key from a given signature.
  • SHA256 (0x0000000000000000000000000000000000000002): Computes the SHA-256 hash of the input data.
  • RIPEMD160 (0x0000000000000000000000000000000000000003): Computes the RIPEMD-160 hash of the input data.
  • IDENTITY (0x0000000000000000000000000000000000000004): Returns the input as the output without any modifications.
  • MODEXP (0x0000000000000000000000000000000000000005): Performs modular exponentiation.
  • BN128_ADD (0x0000000000000000000000000000000000000006): Adds two points on the BN128 elliptic curve.
  • BN128_MUL (0x0000000000000000000000000000000000000007): Multiplies a point on the BN128 elliptic curve by a scalar.
  • BN128_PAIR (0x0000000000000000000000000000000000000008): Performs a pairing check on the BN128 elliptic curve.
  • BLAKE2 (0x0000000000000000000000000000000000000009): Computes the BLAKE2 cryptographic hash of the input data.
  • KZG_POINT_EVALUATION (0x000000000000000000000000000000000000000a): Evaluates a KZG commitment at a given point.
  • BLS12_381_G1_ADD (0x000000000000000000000000000000000000000b): Adds two points in the G1 group on the BLS12-381 elliptic curve.
  • BLS12_381_G1_MUL (0x000000000000000000000000000000000000000c): Multiplies a point in the G1 group on the BLS12-381 elliptic curve by a scalar.
  • BLS12_381_G1_MSM (0x000000000000000000000000000000000000000d): Performs a multi-scalar multiplication in the G1 group on the BLS12-381 elliptic curve.
  • BLS12_381_G2_ADD (0x000000000000000000000000000000000000000e): Adds two points in the G2 group on the BLS12-381 elliptic curve.
  • BLS12_381_G2_MUL (0x000000000000000000000000000000000000000f): Multiplies a point in the G2 group on the BLS12-381 elliptic curve by a scalar.
  • BLS12_381_G2_MSM (0x0000000000000000000000000000000000000010): Performs a multi-scalar multiplication in the G2 group on the BLS12-381 elliptic curve.
  • BLS12_381_PAIRING (0x0000000000000000000000000000000000000011): Performs a pairing check on the BLS12-381 elliptic curve.
  • BLS12_381_MAP_FP_TO_G1 (0x0000000000000000000000000000000000000012): Maps an element of the base field to a point in the G1 group on the BLS12-381 elliptic curve.
  • BLS12_381_MAP_FP2_TO_G2 (0x0000000000000000000000000000000000000013): Maps an element of the quadratic extension field to a point in the G2 group on the BLS12-381 elliptic curve.
  • SECP256R1_VERIFY (0x0000000000000000000000000000000000000100): Verifies an ECDSA signature on the SECP256R1 elliptic curve.

Fluentbase Framework

Fluentbase is a framework offering an SDK and a proving system for Fluent STF. Developers can leverage this framework to create shared applications (smart contracts), dedicated applications, system precompile contracts, or custom STFs.

WARNING: Don't use in production!

Fluentbase is under experimental development and remains a work in progress. The bindings, methods, and naming conventions within the codebase are not yet standardized and may undergo significant changes. Furthermore, the codebase has not been audited or thoroughly tested, which could result in vulnerabilities or crashes.

Modules

  • bin: Contains a binary application used for translating WASM-based applications to rWASM. Necessary only for creating system precompiled contracts requiring direct translation from WASM to rWASM.
  • crates: Houses all Fluentbase modules:
    • codec: A custom ABI codec for encoding/decoding input messages, optimized for random reads to extract necessary information from the system context. It resembles Solidity ABI encoding but uses a more WASM-friendly binary encoding and alignment.
    • contracts: Includes all system precompiled contracts supporting different EE compatibilities like EVM, SVM, WASM, and system contracts (e.g., Blake2, SHA256).
    • core: The core for EE runtimes supporting EVM, SVM, and WASM, including deployment logic, AOT translation, and contract execution.
    • genesis: A program for creating genesis files for the Fluent L2 network with precompiled system and compatibility contracts.
    • poseidon: A library for Poseidon hashing.
    • runtime: The basic execution runtime of rWASM enabling Fluentbase’s host functions.
    • sdk: Provides all required types and methods for developing applications. Includes macros, entry points definitions, allocators, etc.
    • types: Basic primitive types for all crates within the repository.
    • zktrie: Implementation of zktrie (sparse Merkle binary trie).
  • e2e: A set of end-to-end tests for testing EVM transition and other WASM features.
  • revm: A fork of the revm crate, optimized and adapted for Fluentbase SDK methods, mapping the original revm's database objects into Fluentbase’s structures.
  • examples: Contains examples that can be built using the Fluentbase SDK.

Build and Testing

To build Fluentbase, a Makefile is available in the root folder that builds all required dependencies and examples. Run the make command to build all contracts, examples, and genesis files. The resulting files can be found in:

  • crates/contracts/assets: WASM and rWASM binaries for all precompiled contracts and system contracts.
  • crates/genesis/assets: Reth/geth compatible genesis files with injected rWASM binaries (used by reth).
  • examples/*: Each folder contains lib.wasm and lib.wat files matching the compiled example bytecode.

For testing, the complete EVM official testing suite, which consumes significant resources, is included. Increase the Rust stack size to 20 MB for testing:

RUST_MIN_STACK=20000000 cargo test --no-fail-fast

Note: Some tests are still failing (e.g., zktrie), but 99% of them pass.

Examples

Fluentbase SDK can be used to develop various applications, generally using the same interface. Below is a simple application developed using Fluentbase:

#![cfg_attr(target_arch = "wasm32", no_std)]
extern crate fluentbase_sdk;

use fluentbase_sdk::{basic_entrypoint, derive::Contract, SharedAPI};

#[derive(Contract)]
struct GREETING<SDK> {
  sdk: SDK,
}

impl<SDK: SharedAPI> GREETING<SDK> {
  fn deploy(&mut self) {
    // any custom deployment logic here
  }
  fn main(&mut self) {
    // write "Hello, World" message into output
    self.sdk.write("Hello, World".as_bytes());
  }
}

basic_entrypoint!(GREETING);

Supported Languages

Fluentbase SDK currently supports writing smart contracts in:

  • Rust
  • Solidity
  • Vyper

Fluentbase Operation

Fluentbase operates using Fluent's rWASM VM (reduced WebAssembly). This VM uses a 100% compatible WebAssembly binary representation optimized for Zero-Knowledge (ZK) operations. The instruction set is reduced, with sections embedded inside the binary to simplify the proving process.

Limitations and Future Enhancements

As of now, Fluentbase does not support floating-point operations. However, this feature is on the roadmap for future enhancements.