๐Ÿ’Ž Ethereum

โ† Target Reference | VM: EVM

Ethereum is the canonical EVM chain -- L1 settlement layer. Trident compiles to EVM bytecode (.evm) and links against os.ethereum.* for Ethereum- specific runtime bindings. Same bytecode runs on all EVM-compatible chains with different os.* bindings.


Runtime Parameters

Parameter Value
VM EVM
Runtime binding os.ethereum.*
Account model Account
Storage model Key-value (SLOAD/SSTORE)
Transaction model Signed (ECDSA)
Cost model Gas
Cross-chain -- (canonical L1)

Programming Model

Entry Points

Ethereum programs are contracts -- deployed bytecode with exported functions. A contract has:

  • Constructor -- runs once at deployment, initializes storage
  • External functions -- callable by transactions or other contracts
  • View functions -- read-only, no state mutation
program my_token

use os.ethereum.storage
use os.ethereum.account

// Constructor: called once at deployment
#[constructor]
fn init(supply: Field) {
    let deployer: Field = os.ethereum.account.caller()
    os.ethereum.storage.write(0, supply)           // slot 0 = total supply
    os.ethereum.storage.write_map(1, deployer, supply)  // slot 1 = balances mapping
}

// External function: transfer tokens
pub fn transfer(to: Field, amount: Field) {
    let sender: Field = os.ethereum.account.caller()
    let sender_bal: Field = os.ethereum.storage.read_map(1, sender)
    let to_bal: Field = os.ethereum.storage.read_map(1, to)

    assert(sender_bal >= amount)
    os.ethereum.storage.write_map(1, sender, sub(sender_bal, amount))
    os.ethereum.storage.write_map(1, to, to_bal + amount)

    reveal Transfer { from: sender, to: to, amount: amount }
}

// View function: read balance
#[view]
pub fn balance_of(owner: Field) -> Field {
    os.ethereum.storage.read_map(1, owner)
}

State Access

Ethereum contracts have persistent key-value storage. Each contract has 2^256 storage slots, accessed by SLOAD/SSTORE.

use os.ethereum.storage

// Direct slot access
let value: Field = os.ethereum.storage.read(slot)
os.ethereum.storage.write(slot, value)

// Mapping access (Solidity-style: keccak256(key . slot))
let bal: Field = os.ethereum.storage.read_map(slot, key)
os.ethereum.storage.write_map(slot, key, value)

// Nested mapping (keccak256(key2 . keccak256(key1 . slot)))
let allowance: Field = os.ethereum.storage.read_map2(slot, owner, spender)
os.ethereum.storage.write_map2(slot, owner, spender, value)

Storage layout follows Solidity conventions: slot 0 is the first declared variable, mappings use keccak256(key . slot) for index computation. The compiler handles the encoding automatically.

Identity and Authorization

Ethereum provides protocol-level identity via transaction signatures. The EVM injects the caller's address before the program runs.

use os.ethereum.account

let caller: Field = os.ethereum.account.caller()      // msg.sender
let origin: Field = os.ethereum.account.origin()       // tx.origin
let self_addr: Field = os.ethereum.account.self_address()  // address(this)

// Ownership check
assert(caller == owner)

No reentrancy. Trident programs are sequential with bounded loops. There is no callback mechanism, no fallback function, no way for an external call to re-enter the current contract mid-execution.

Value Transfer

ETH transfers use the native transfer mechanism:

use os.ethereum.transfer
use os.ethereum.account

let my_balance: Field = os.ethereum.account.balance(self_addr)
os.ethereum.transfer.send(recipient, amount)

ERC-20 token operations are implemented as contract calls (see Cross-Contract Interaction below).

Cross-Contract Interaction

The EVM supports several call types:

use os.ethereum.call

// Regular call (can transfer ETH + call function)
let result: [Field; N] = os.ethereum.call.call(
    target_address, value, calldata
)

// Static call (read-only, reverts on state change)
let result: [Field; N] = os.ethereum.call.static_call(
    target_address, calldata
)

// Delegate call (runs target code in caller's storage context)
let result: [Field; N] = os.ethereum.call.delegate_call(
    target_address, calldata
)

// Return data from last call
let data: [Field; N] = os.ethereum.call.return_data()

Events

EVM events use LOG0-LOG4 opcodes with indexed topics:

event Transfer { from: Field, to: Field, amount: Field }
event Approval { owner: Field, spender: Field, amount: Field }

// reveal compiles to LOG with indexed topics
reveal Transfer { from: sender, to: receiver, amount: value }

reveal maps to LOG opcodes. seal has no native EVM equivalent -- the compiler emits only the commitment hash as a LOG topic.


Portable Alternative (os.*)

Programs that don't need Ethereum-specific features can use os.* instead of os.ethereum.* for cross-chain portability:

os.ethereum.* (this OS only) os.* (any OS)
os.ethereum.storage.read(slot) os.state.read(key) โ†’ SLOAD
os.ethereum.account.caller() os.neuron.id() โ†’ msg.sender (padded to Digest)
os.ethereum.transfer.send(to, amt) os.signal.send(from, to, amt) โ†’ CALL with value (self) / transferFrom (delegated)
os.ethereum.block.timestamp() os.time.now() โ†’ block.timestamp

Use os.ethereum.* when you need: precompiles, delegatecall, specific LOG topics, storage maps, or other EVM-specific features. See os.md for the full os.* API.


Ecosystem Mapping

Solidity concept Trident equivalent
contract MyToken { } program my_token with use os.ethereum.*
constructor(uint supply) #[constructor] fn init(supply: Field)
function transfer() external pub fn transfer()
function balanceOf() view #[view] pub fn balance_of()
msg.sender os.ethereum.account.caller()
tx.origin os.ethereum.account.origin()
address(this) os.ethereum.account.self_address()
address(this).balance os.ethereum.account.balance(self_addr)
mapping(address => uint) os.ethereum.storage.read_map(slot, key)
SLOAD(slot) os.ethereum.storage.read(slot)
SSTORE(slot, val) os.ethereum.storage.write(slot, value)
payable.transfer(amount) os.ethereum.transfer.send(to, amount)
target.call(data) os.ethereum.call.call(target, value, data)
target.staticcall(data) os.ethereum.call.static_call(target, data)
target.delegatecall(data) os.ethereum.call.delegate_call(target, data)
emit Transfer(from, to, amount) reveal Transfer { from, to, amount }
block.number os.ethereum.block.number()
block.timestamp os.ethereum.block.timestamp()
block.coinbase os.ethereum.block.coinbase()
block.basefee os.ethereum.block.base_fee()
block.chainid os.ethereum.block.chain_id()
tx.gasprice os.ethereum.tx.gas_price()
gasleft() os.ethereum.tx.gas_remaining()
require(cond, "msg") assert(cond) (no error messages -- revert or succeed)
revert("msg") assert(false)
ecrecover(hash, v, r, s) os.ethereum.precompile.ecrecover(hash, v, r, s)
keccak256(data) hash(...) (uses VM-native hash on Triton; Keccak on EVM)

os.ethereum.* API Reference

Module Function Signature Description
storage read(slot) Field -> Field SLOAD
write(slot, val) (Field, Field) -> () SSTORE
read_map(slot, key) (Field, Field) -> Field Mapping read
write_map(slot, key, val) (Field, Field, Field) -> () Mapping write
read_map2(slot, k1, k2) (Field, Field, Field) -> Field Nested mapping read
write_map2(slot, k1, k2, val) (Field, Field, Field, Field) -> () Nested mapping write
account caller() -> Field msg.sender
origin() -> Field tx.origin
self_address() -> Field address(this)
balance(addr) Field -> Field Address balance
transfer send(to, amount) (Field, Field) -> () ETH transfer
call call(addr, value, data) (Field, Field, [Field]) -> [Field] External call
static_call(addr, data) (Field, [Field]) -> [Field] Read-only call
delegate_call(addr, data) (Field, [Field]) -> [Field] Delegated call
return_data() -> [Field] Last call return data
event log(topics, data) ([Field], [Field]) -> () Raw LOG
block number() -> Field block.number
timestamp() -> Field block.timestamp
coinbase() -> Field block.coinbase
base_fee() -> Field block.basefee
chain_id() -> Field block.chainid
tx gas_price() -> Field tx.gasprice
gas_remaining() -> Field gasleft()
precompile ecrecover(hash, v, r, s) (Field, Field, Field, Field) -> Field ECDSA recovery
sha256(data) [Field] -> Digest SHA-256 precompile

Notes

The EVM is a 256-bit stack machine. Trident's field elements are mapped to u256 values. Field arithmetic becomes modular arithmetic over the EVM's native 256-bit word. Storage layout follows Solidity conventions for compatibility with existing tooling (etherscan, foundry, hardhat).

For VM details, see evm.md. For mental model migration from Solidity, see For Onchain Devs.

Dimensions

ethereum

Pages in this namespace

Local Graph