Skip to content
Pre-Release: Fe is under active development. This documentation covers the upcoming release. Follow progress on GitHub

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 = 0xa9059cbb]
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:

struct Transfer {
#[indexed]
from: Address,
#[indexed]
to: Address,
value: u256,
}
log.emit(Transfer { from, to, value: amount })

Solidity:

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

Fe:

assert(balance >= amount, "Insufficient balance")
revert

Solidity:

constructor(uint256 initialSupply) {
totalSupply = initialSupply;
}

Fe:

contract Token {
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: Map<(Address, Address), u256>,
}
store.allowances[(owner, spender)] = amount
let allowed = store.allowances[(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, "Contract is paused")
}
// In handler:
require_not_paused(store.paused)

Fe requires explicit ABI selectors:

msg TokenMsg {
#[selector = 0xa9059cbb] // 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 = 10 // u256 inferred
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->