Message Fields
Message fields define the parameters that callers pass when invoking a message variant.
Field Syntax
Section titled “Field Syntax”Fields are defined inside curly braces, similar to struct fields:
#[selector = sol("transfer(address,uint256)")] Transfer { to: Address, amount: u256 } -> bool,Each field has:
- A name (
to,amount) - A type (
u256)
Multiple fields are separated by commas.
Supported Types
Section titled “Supported Types”Message fields must support the selected ABI. Use ABI-compatible primitives, tuples, and the standard library’s dynamic ABI types; internal types such as storage maps and pointers are not message parameters.
Primitive Types
Section titled “Primitive Types”use std::abi::sol
msg Example { #[selector = sol("withPrimitives(bool,uint256,int128)")] WithPrimitives { flag: bool, count: u256, signed_value: i128, },}Compound Types
Section titled “Compound Types”use std::abi::sol
msg Example { #[selector = sol("withTuple((uint256,uint256))")] WithTuple { coords: (u256, u256) } -> bool,
}No Fields
Section titled “No Fields”Variants can have no fields:
use std::abi::sol
msg Query { #[selector = sol("totalSupply()")] TotalSupply -> u256,
#[selector = sol("name()")] Name -> String<31>,}Field Order Matters
Section titled “Field Order Matters”Field order is significant for ABI encoding. The order in which fields are defined determines how calldata is decoded:
// These are different!msg Example1 { #[selector = sol("transfer(address,uint256)")] Transfer { to: Address, amount: u256 },}
msg Example2 { #[selector = sol("transfer(uint256,address)")] Transfer { amount: u256, to: Address },}These two signatures have different selectors. Keeping the first signature while reversing the fields is a compile error in Fe. When implementing standard interfaces like ERC20, preserve the specified types and field order.
Accessing Fields in Handlers
Section titled “Accessing Fields in Handlers”In recv blocks, destructure fields to access their values:
recv TokenMsg { Transfer { to, amount } -> bool { // 'to' and 'amount' are available here true } }You can also rename fields during destructuring:
recv TokenMsg { Transfer { to: recipient, amount: value } -> bool { // Use 'recipient' and 'value' instead true } }Ignoring Fields
Section titled “Ignoring Fields”Use _ to ignore fields you don’t need:
recv TokenMsg { Transfer { to, amount: _ } -> bool { // Only use 'to', ignore amount true } }Use .. to ignore remaining fields:
recv TokenMsg { TransferFrom { from, .. } -> bool { // Only use 'from', ignore to and amount true } }Structured Parameters
Section titled “Structured Parameters”Mark a struct #[abi] to use it as a message field or return type. The compiler derives AbiSize, Encode<Sol> and Decode<Sol> for it, encoding the struct like the Solidity tuple of its fields, so the selector spells it as a tuple:
use std::abi::sol
#[abi]pub struct Position { pub owner: Address, pub amount: u256,}
#[event]struct Opened { #[indexed] owner: Address, position: Position,}
msg PositionMsg { #[selector = sol("open((address,uint256))")] Open { position: Position } -> Position,}
pub contract Positions uses (log: mut Log) { recv PositionMsg { Open { position } -> Position uses (mut log) { let doubled = Position { owner: position.owner, amount: position.amount * 2 } log.emit(event: Opened { owner: position.owner, position }) doubled } }}
#[test]fn round_trips_abi_struct() uses (evm: mut Evm) { let target = evm.create2<Positions>(value: Wei::zero(), args: (), salt: 0) let result: Position = evm.call( addr: target, gas: 1000000, value: Wei::zero(), message: PositionMsg::Open { position: Position { owner: Address::new(7), amount: 21 } }, ) assert!(result.owner == Address::new(7)) assert!(result.amount == 42)}#[abi] structs can be nested inside other #[abi] structs, used in arrays, and used as #[event] and #[error] fields. A plain struct without #[abi] cannot cross the message boundary; either mark it #[abi] or use a tuple and convert it in the handler.
Summary
Section titled “Summary”| Pattern | Meaning |
|---|---|
{ field: Type } | Named field with type |
{ a, b, c } | Multiple fields |
| (no braces) | No parameters |
{ field } | Destructure keeping name |
{ field: name } | Destructure with rename |
{ field: _ } | Ignore specific field |
{ field, .. } | Ignore remaining fields |
{ field: (A, B) } | Tuple field (use instead of a struct) |