Skip to content
Fe 26.0 is not production-ready. This is an initial release of a new compiler. Learn more

Fe for Solidity Developers

This guide helps Solidity developers understand Fe by highlighting key differences and showing equivalent patterns.

Solidity: Functions can access any state variable implicitly.

contract Token {
mapping(address => uint256) balances;
function transfer(address to, uint256 amount) public {
balances[msg.sender] -= amount; // Implicit access
balances[to] += amount;
}
}

Fe: Functions must declare what state they access via effects.

fn transfer(from: Address, to: Address, amount: u256)
uses mut store: TokenStore // Explicit declaration
{
store.balances[from] -= amount
store.balances[to] += amount
}

Solidity: Functions are defined directly in contracts.

contract Token {
function transfer(address to, uint256 amount) public returns (bool) {
// ...
}
}

Fe: External interface is defined separately via messages.

msg TokenMsg {
#[selector = sol("transfer(address,uint256)")]
Transfer { to: Address, amount: u256 } -> bool,
}
contract Token {
recv TokenMsg {
Transfer { to, amount } -> bool {
// ...
}
}
}

Solidity: Direct access to state variables.

balances[msg.sender] = 100;

Fe: Access through effect-bound storage.

// In recv block
with (TokenStorage = store) {
TokenStorage.balances[caller()] = 100
}
SolidityFe
uint256 x = 10;let x: u256 = 10
uint256 x;let x: u256 = 0
address owner;let owner: Address
bool active = true;let active: bool = true
SolidityFe
uint256u256
int256i256
uint8u8
addressAddress
boolbool
stringString<N>
bytes32u256
mapping(K => V)Map<K, V>

Solidity:

function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}

Fe:

fn add(a: u256, b: u256) -> u256 {
a + b
}
SolidityFe
publicpub
private(default)
internal(default within module)
externalvia msg and recv

Solidity:

if (x > 0) {
return true;
} else {
return false;
}
for (uint i = 0; i < 10; i++) {
// ...
}

Fe:

if x > 0 {
return true
} else {
return false
}
let mut i: u256 = 0
while i < 10 {
// ...
i = i + 1
}

Solidity:

event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(...) {
emit Transfer(from, to, amount);
}

Fe:

#[event]
struct Transfer {
#[indexed]
from: Address,
#[indexed]
to: Address,
value: u256,
}
fn transfer(from: Address, to: Address, amount: u256) uses (log: mut Log) {
log.emit(event: Transfer { from, to, value: amount })
}

Solidity:

require(balance >= amount, "Insufficient balance");
revert("Error message");

Fe:

assert(balance >= amount)
revert("Error message")

Solidity:

constructor(uint256 initialSupply) {
totalSupply = initialSupply;
}

Fe:

contract Token {
mut store: TokenStore
init(initial_supply: u256) uses (mut store) {
store.total_supply = initial_supply
}
}

Solidity:

function transfer(address to, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}

Fe:

Transfer { to, amount } -> bool uses (ctx, mut store, mut log) {
let from = ctx.caller()
assert(store.balances[from] >= amount, "Insufficient balance")
store.balances[from] -= amount
store.balances[to] += amount
log.emit(TransferEvent { from, to, value: amount })
true
}

Solidity:

modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function mint(address to, uint256 amount) public onlyOwner {
// ...
}

Fe:

fn require_owner(owner: Address) uses ctx: Ctx {
assert(ctx.caller() == owner, "Not owner")
}
Mint { to, amount } -> bool uses (ctx, mut store, auth) {
auth.require(role: MINTER) // Or: require_owner(store.owner)
// ...
}

Solidity:

mapping(address => mapping(address => uint256)) allowances;
allowances[owner][spender] = amount;
uint256 allowed = allowances[owner][spender];

Fe:

struct Storage {
allowances: StorageMap<(Address, Address), u256>,
}
store.allowances.set(key: (owner, spender), value: amount)
let allowed = store.allowances.get((owner, spender))

Fe doesn’t have contract inheritance. Use composition instead:

// Instead of: contract Token is Ownable, Pausable
contract Token {
auth: AccessControl // Composition
pause_state: Pausable // Composition
}

Fe doesn’t have function modifiers. Use helper functions:

fn require_not_paused(paused: bool) {
assert(!paused)
}
// In handler:
require_not_paused(paused: false)

Fe requires explicit ABI selectors:

msg TokenMsg {
#[selector = sol("transfer(address,uint256)")] // Must specify
Transfer { to: Address, amount: u256 } -> bool,
}

Fe doesn’t support function overloading. Use different names:

// Instead of transfer(address) and transfer(address, uint256)
fn transfer_to(to: Address) {
}
fn transfer_amount(to: Address, amount: u256) {
}

Fe infers types where possible:

let x: u256 = 10 // u256 type
let y: u8 = 10 // Explicit when needed
  1. Start with messages - Define your external interface first with explicit selectors

  2. Group storage - Create storage structs instead of individual state variables

  3. Add effect annotations - Every function touching state needs uses clauses

  4. Replace modifiers - Convert to helper functions with assertions

  5. Use composition - Replace inheritance with struct fields

  6. Test selectors - Verify your selectors match the Solidity ABI

SolidityFe
msg.senderctx.caller()
block.timestampctx.block_timestamp()
block.numberctx.block_number()
require(...)assert(...)
emit Event(...)log.emit(Event { ... })
mapping(K => V)Map<K, V>
constructorinit
functionfn
publicpub
returns->