ABI encode and decode
Abi provides Solidity's encoding helpers.
abi.lithe
(* `Abi` mirrors Solidity's `abi.*` helpers. *)
import "std/abi";
import "std/evm";
interface Token is {
function transfer(to : address; amount : uint) : bool;
}
storage is record {
last : bytes;
}
external pure function encode(const to : address; const amount : uint) : bytes is
Abi.encode(to, amount)
external pure function decode(const data : bytes) : uint is
Abi.decode(data, uint)
external pure function packed(const tag : string; const who : address) : bytes is
Abi.encodePacked(tag, who)
(* Calldata for `transfer(address,uint256)`, three ways. *)
external pure function bySelector(const to : address; const amount : uint) : bytes is
Abi.encodeWithSelector(0xa9059cbb, to, amount)
external pure function bySignature(const to : address; const amount : uint) : bytes is
Abi.encodeWithSignature("transfer(address,uint256)", to, amount)
external pure function byCall(const to : address; const amount : uint) : bytes is
Abi.encodeCall(Token.transfer, to, amount)
(* The raw calldata of the current call. *)
external function remember() is {
storage.last := Evm.calldata();
}Abi.decodetakes a type name as its second argument.bySelector,bySignature, andbyCallproduce identical bytes. OnlyencodeCallchecks the arguments against the method's parameter types.
Several values, and nested ones
A tuple of type names decodes one value of each, as Solidity's abi.decode(data, (A, B)) does. Lists and records nest to any depth, both ways:
nested.lithe
import "std/abi";
type Order is record {
id : uint;
note : string;
legs : list(uint);
}
type Orders is list(Order);
type Transfer is record {
to : address;
amount : uint;
memo : string;
}
storage is record {
n : uint;
}
external pure function unpack(const data : bytes) : Transfer is {
const (to, amount, memo) : (address, uint, string) := Abi.decode(data, (address, uint, string));
} with { to = to; amount = amount; memo = memo; }
external pure function roundTrip(const os : list(Order)) : list(Order) is
Abi.decode(Abi.encode(os), Orders)
external pure function secondLeg(const data : bytes) : uint is {
const os : list(Order) := Abi.decode(data, Orders);
} with os[0].legs[1]- A record is one Solidity tuple wherever it is a value:
Abi.encode(os)isabi.encode(Order[]). - Decoding checks every offset, length, and word as Solidity's decoder does, and malformed data reverts.
Calldata and answers of your own
With a raw call, you build the calldata and decode the answer yourself:
raw.lithe
import "std/abi";
import "std/evm";
interface Token is {
view function balanceOf(owner : address) : uint;
}
storage is record {
n : uint;
}
external view function balance(const token : address; const owner : address) : uint is
Abi.decode(query Evm.staticCall(token, Abi.encodeCall(Token.balanceOf, owner)), uint)
external view function reserves(const pair : address) : (uint112, uint112) is {
const answer : bytes := query Evm.staticCall(pair, Abi.encodeWithSignature("getReserves()"));
const (r0, r1, last) : (uint112, uint112, uint32) := Abi.decode(answer, (uint112, uint112, uint32));
} with (r0, r1)examples/abi_codec.lithe and examples/raw_codec.lithe go further, and bench/codec and bench/dialer compare the same patterns against Solidity, byte for byte.

