Fe for Solidity Developers
This guide helps Solidity developers understand Fe by highlighting key differences and showing equivalent patterns.
Key Conceptual Differences
Section titled “Key Conceptual Differences”Explicit Effects vs Implicit State
Section titled “Explicit Effects vs Implicit State”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}Contracts vs Messages
Section titled “Contracts vs Messages”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 { // ... } }}Storage Access
Section titled “Storage Access”Solidity: Direct access to state variables.
balances[msg.sender] = 100;Fe: Access through effect-bound storage.
// In recv blockwith (TokenStorage = store) { TokenStorage.balances[caller()] = 100}Syntax Comparison
Section titled “Syntax Comparison”Variable Declaration
Section titled “Variable Declaration”| Solidity | Fe |
|---|---|
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 |
| Solidity | Fe |
|---|---|
uint256 | u256 |
int256 | i256 |
uint8 | u8 |
address | Address |
bool | bool |
string | String<N> |
bytes32 | u256 |
mapping(K => V) | Map<K, V> |
Functions
Section titled “Functions”Solidity:
function add(uint256 a, uint256 b) public pure returns (uint256) { return a + b;}Fe:
fn add(a: u256, b: u256) -> u256 { a + b}Visibility
Section titled “Visibility”| Solidity | Fe |
|---|---|
public | pub |
private | (default) |
internal | (default within module) |
external | via msg and recv |
Control Flow
Section titled “Control Flow”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 = 0while i < 10 { // ... i = i + 1}Events
Section titled “Events”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 })Error Handling
Section titled “Error Handling”Solidity:
require(balance >= amount, "Insufficient balance");revert("Error message");Fe:
assert(balance >= amount, "Insufficient balance")revertConstructors
Section titled “Constructors”Solidity:
constructor(uint256 initialSupply) { totalSupply = initialSupply;}Fe:
contract Token { init(initial_supply: u256) uses mut store { store.total_supply = initial_supply }}Pattern Equivalents
Section titled “Pattern Equivalents”ERC20 Transfer
Section titled “ERC20 Transfer”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}Access Control
Section titled “Access Control”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) // ...}Mappings
Section titled “Mappings”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)] = amountlet allowed = store.allowances[(owner, spender)]What’s Different in Fe
Section titled “What’s Different in Fe”No Inheritance
Section titled “No Inheritance”Fe doesn’t have contract inheritance. Use composition instead:
// Instead of: contract Token is Ownable, Pausablecontract Token { auth: AccessControl, // Composition pause_state: Pausable, // Composition}No Modifiers
Section titled “No Modifiers”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)Explicit Selectors
Section titled “Explicit Selectors”Fe requires explicit ABI selectors:
msg TokenMsg { #[selector = 0xa9059cbb] // Must specify Transfer { to: Address, amount: u256 } -> bool,}No Overloading
Section titled “No Overloading”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) { ... }Type Inference
Section titled “Type Inference”Fe infers types where possible:
let x = 10 // u256 inferredlet y: u8 = 10 // Explicit when neededMigration Tips
Section titled “Migration Tips”-
Start with messages - Define your external interface first with explicit selectors
-
Group storage - Create storage structs instead of individual state variables
-
Add effect annotations - Every function touching state needs
usesclauses -
Replace modifiers - Convert to helper functions with assertions
-
Use composition - Replace inheritance with struct fields
-
Test selectors - Verify your selectors match the Solidity ABI
Quick Reference
Section titled “Quick Reference”| Solidity | Fe |
|---|---|
msg.sender | ctx.caller() |
block.timestamp | ctx.block_timestamp() |
block.number | ctx.block_number() |
require(...) | assert(...) |
emit Event(...) | log.emit(Event { ... }) |
mapping(K => V) | Map<K, V> |
constructor | init |
function | fn |
public | pub |
returns | -> |