Skip to content

RISC-V Syscall Interface Documentation

Overview

The syscall interface system enables RISC-V smart contracts to interact with the EVM host environment in the Hybrid Framework. This interface provides a bridge between RISC-V execution contexts and EVM blockchain state operations, including storage access, account queries, and block information retrieval.

Related Documentation:
  • RISC-V Emulation
  • Mini EVM Interpreter

Architecture Overview

The syscall interface operates as a bidirectional communication layer between RISC-V contracts running in the rvemu emulator and the EVM host environment managed by hybrid-vm. This system allows RISC-V smart contracts to access blockchain state and invoke EVM operations through a standardized syscall mechanism.

System Call Flow

┌─────────────────────────┐
│  RISC-V Contract        │
│  Rust Smart Contract    │
└───────────┬─────────────┘

            │ ecall instruction
            │ t0 = syscall_id
            │ a0-a6 = arguments

┌─────────────────────────┐
│  Syscall Processing     │
│  (execution/mod.rs)     │
└───────────┬─────────────┘

     ┌──────┴──────┐
     │             │
     ▼             ▼
┌─────────┐   ┌─────────────┐
│  HOST_* │   │ EVM Opcode  │
│  ops    │   │  Wrappers   │
└────┬────┘   └──────┬──────┘
     │               │
     └───────┬───────┘

    ┌─────────────────┐
    │  Shared Memory  │
    │  0xBEC00000     │
    └────────┬────────┘


    ┌─────────────────┐
    │  EVM Host       │
    │  Environment    │
    └────────┬────────┘


    ┌─────────────────┐
    │ Blockchain State│
    └─────────────────┘
Key Components:
  • RISC-V Contract Execution: Smart contracts written in Rust compiled to RISC-V
  • Syscall Processing Layer: Routes syscalls to appropriate handlers
  • Host Interface Layer: Manages communication with EVM host
  • Shared Memory: MINI_EVM_SYSCALLS_MEM_ADDR at 0xBEC00000
  • EVM Host Environment: Provides access to blockchain state

Syscall Categories

The syscall interface supports several categories of operations:

CategorySyscall IDsPurposeImplementation
Host State Queries10-14, 19Account and block informationext_syscalls.rs
Storage Operations15-16Persistent storage accessDirect host calls
Transient Storage17-18EIP-1153 temporary storageRegister-based I/O
Contract Operations20Self-destruct functionalitySerialized host calls
EVM Standard Opcodes0x20-0xFFStandard EVM operationsexecution/mod.rs

Host Operations (IDs 10-20)

IDSyscallFunctionDescription
10HOST_BALANCEhost_balance()Query account balance
11HOST_LOAD_ACCOUNT_CODEhost_load_account_code()Load contract bytecode
12HOST_LOAD_ACCOUNT_CODE_HASHhost_load_account_code_hash()Get code hash
13HOST_BLOCK_NUMBERhost_block_number()Current block number
14HOST_BLOCK_HASHhost_block_hash()Block hash by number
15HOST_SLOADhost_sload()Load from storage
16HOST_SSTOREhost_sstore()Store to storage
17HOST_TLOADhost_tload()Load from transient storage
18HOST_TSTOREhost_tstore()Store to transient storage
19HOST_LOAD_ACCOUNT_DELEGATEDhost_load_account_delegated()Query delegated account
20HOST_SELFDESTRUCThost_selfdestruct()Self-destruct contract

EVM Opcodes (IDs 0x20-0xFF)

OpcodeIDSyscallDescription
KECCAK2560x20Syscall::Keccak256Compute Keccak-256 hash
BALANCE0x31Syscall::BalanceGet account balance
SLOAD0x54Syscall::SLoadLoad storage slot
SSTORE0x55Syscall::SStoreStore to storage slot
CREATE0xF0Syscall::CreateCreate new contract
CALL0xF1Syscall::CallCall another contract
RETURN0xF3Syscall::ReturnReturn from execution
REVERT0xFDSyscall::RevertRevert state changes

Memory Management

Memory Layout

The syscall interface uses a dedicated memory region for data exchange:

  • Base Address: MINI_EVM_SYSCALLS_MEM_ADDR = 0xBEC00000
  • Location: Last 20MB of address space
  • Serialization Format: bincode with legacy configuration
  • Data Flow: Input parameters → shared memory → host processing → output results

Memory Usage Pattern

┌──────────────┐
│  EVM Host    │
└──────┬───────┘
       │ 1. Write serialized input

┌──────────────────────┐
│  Shared Memory       │
│  0xBEC00000          │
└──────┬───────────────┘
       │ 2. ecall with input_size

┌──────────────────────┐
│  RISC-V Contract     │
│  Read input data     │
│  Process syscall     │
└──────┬───────────────┘
       │ 3. Write serialized output

┌──────────────────────┐
│  Shared Memory       │
│  0xBEC00000          │
└──────┬───────────────┘
       │ 4. Return output_size

┌──────────────────────┐
│  EVM Host            │
│  Read output data    │
└──────────────────────┘

Implementation Details

Host Syscall Functions

The ext_syscalls.rs module implements host-specific syscalls using shared memory communication.

Example: SSTORE Implementation

pub fn host_sstore(address: Address, index: U256, value: U256) 
    -> Option<StateLoad<SStoreResult>> {
    // Serialize input parameters
    let input_serialized = serialize_sstore_input(address, index, value);
    let input_size = input_serialized.len();
    
    // Write to shared memory
    unsafe {
        let dest = slice_from_raw_parts_mut(
            MINI_EVM_SYSCALLS_MEM_ADDR, 
            input_serialized.len()
        );
        dest.copy_from_slice(&input_serialized);
    }
    
    // Make syscall via ecall instruction
    let mut output_size;
    unsafe {
        asm!(
            "ecall",
            in("a0") input_size,
            lateout("a0") output_size,
            in("t0") mini_evm_syscalls_ids::HOST_SSTORE
        );
    }
    
    // Read result from shared memory
    let out_serialized = unsafe { 
        slice_from_raw_parts(MINI_EVM_SYSCALLS_MEM_ADDR, output_size) 
    };
    
    bincode::serde::decode_from_slice(
        out_serialized, 
        bincode::config::legacy()
    ).unwrap().0
}

EVM Opcode Wrappers

The ext_opcode.rs module provides EVM opcode implementations.

Example: BALANCE Opcode

pub fn balance<WIRE: InterpreterTypes, H: Host + ?Sized>(
    interpreter: &mut Interpreter<WIRE>,
    _host: &mut H,
) {
    popn_top!([], top, interpreter);
    let address = top.into_address();
    
    // Call host_balance syscall
    let Some(balance) = host_balance(address) else {
        interpreter.control.set_instruction_result(
            InstructionResult::FatalExternalError
        );
        return;
    };
    
    // Calculate gas cost based on EVM specification
    gas!(interpreter, 
        if spec_id.is_enabled_in(BERLIN) {
            warm_cold_cost(balance.is_cold)
        } else if spec_id.is_enabled_in(ISTANBUL) {
            700
        } else if spec_id.is_enabled_in(TANGERINE) {
            400
        } else {
            20
        }
    );
    
    *top = balance.data;
}

RISC-V Execution Loop

The main execution loop in hybrid-vm processes syscalls:

match run_result {
    Err(Exception::EnvironmentCallFromMMode) => {
        // Read syscall ID from t0 register
        let t0: u64 = emu.cpu.xregs.read(5);
        
        let Ok(syscall) = Syscall::try_from(t0 as u8) else {
            return return_revert(
                interpreter, 
                interpreter.control.gas.spent()
            );
        };
        
        match syscall {
            Syscall::Balance => {
                // Read address from registers a0-a2
                let address_1 = emu.cpu.xregs.read(10);
                let address_2 = emu.cpu.xregs.read(11);
                let address_3 = emu.cpu.xregs.read(12);
                
                let address = __3u64_to_address(
                    address_1, address_2, address_3
                );
                
                match host.balance(address) {
                    Some(state_load) => {
                        // Write result to registers a0-a3
                        let limbs = state_load.data.as_limbs();
                        emu.cpu.xregs.write(10, limbs[0]);
                        emu.cpu.xregs.write(11, limbs[1]);
                        emu.cpu.xregs.write(12, limbs[2]);
                        emu.cpu.xregs.write(13, limbs[3]);
                    }
                    _ => return return_revert(
                        interpreter, 
                        interpreter.control.gas.spent()
                    ),
                }
            }
            // ... other syscalls
        }
    }
}

Register Conventions

The syscall interface follows RISC-V calling conventions:

RegisterRISC-V NamePurposeUsage
t0x5Syscall IDContains the syscall identifier
a0x10Argument 0 / Return 0First argument or return value
a1x11Argument 1 / Return 1Second argument or return value
a2x12Argument 2 / Return 2Third argument or return value
a3x13Argument 3 / Return 3Fourth argument or return value
a4x14Argument 4Fifth argument
a5x15Argument 5Sixth argument
a6x16Argument 6Seventh argument

Complex Data Type Encoding

U256 Values:
  • Split across 4 registers (a0-a3)
  • Each register contains a 64-bit limb
  • Little-endian ordering
Addresses:
  • Split across 3 registers (a0-a2)
  • Zero-padded to fit 160-bit address
  • Remaining bits unused
Variable-Length Data:
  • Size passed in a0
  • Data stored in shared memory at 0xBEC00000
  • Read/write using memory address

Error Handling

The syscall interface implements comprehensive error handling:

Error Types

Error TypeCauseResult
Invalid Syscall IDUnknown syscall identifierContract reversion
Memory Access ErrorInvalid memory operationFatalExternalError
Host Operation FailureFailed state operationContract revert
Serialization ErrorInvalid data formatPanic or revert

Error Flow

Syscall Invoked

Validate Syscall ID ──→ Invalid ──→ Revert Contract
    ↓ Valid
Execute Operation ──→ Failure ──→ FatalExternalError/Revert
    ↓ Success
Return Result

State Consistency

The system ensures that any failure in the syscall interface results in a clean revert of the RISC-V contract execution, maintaining blockchain state consistency. No partial state changes are committed when a syscall fails.

Usage Examples

Calling a Syscall from RISC-V Contract

// Example: Query account balance
use hybrid_syscalls::*;
 
fn get_balance(address: Address) -> U256 {
    // This will trigger a syscall via the ecall instruction
    let balance = host_balance(address).expect("Balance query failed");
    balance.data
}

Storage Operations

// Store a value
let key = U256::from(1);
let value = U256::from(42);
host_sstore(contract_address, key, value);
 
// Load a value
let stored_value = host_sload(contract_address, key)
    .expect("Storage load failed");

Transient Storage (EIP-1153)

// Store temporary data (cleared at transaction end)
host_tstore(key, value);
 
// Load temporary data
let temp_value = host_tload(key);

Source Code References

  • Syscall Definitions: crates/hybrid-syscalls/src/lib.rs
  • Host Syscall Implementation: bins/mini-evm-interpreter/src/ext_syscalls.rs
  • EVM Opcode Wrappers: bins/mini-evm-interpreter/src/ext_opcode.rs
  • Execution Loop: crates/hybrid-vm/src/execution/mod.rs