sol!() { /* proc-macro */ }
Expand description
Generate types that implement alloy-sol-types
traits, which can be used
for type-safe ABI and EIP-712 serialization to interface with Ethereum
smart contracts.
Note that you will likely want to use this macro through a re-export in another crate,
as it will also set the correct paths for the required dependencies by using a macro_rules!
wrapper.
§Examples
Note: the following example code blocks cannot be tested here because the generated code references
alloy-sol-types
, so they are tested in that crate and included withinclude_str!
in this doc instead.
There are two main ways to use this macro:
- you can write Solidity code, or provide a path to a Solidity file,
- if you enable the
json
feature, you can provide an ABI, or a path to one, in JSON format.
Note:
- relative file system paths are rooted at the
CARGO_MANIFEST_DIR
environment variable - no casing convention is enforced for any identifier,
- unnamed arguments will be given a name based on their index in the list, e.g.
_0
,_1
… - a current limitation for certain items is that custom types, like structs, must be defined in the same macro scope, otherwise a signature cannot be generated at compile time. You can bring them in scope with a Solidity type alias.
§Solidity
This macro uses syn-solidity
to parse Solidity-like syntax. See
its documentation for more.
Solidity input can be either one of the following:
- a Solidity item, which is a Solidity source unit which generates one or more Rust items,
- a Solidity type name, which simply expands to the corresponding Rust type.
IMPORTANT! This is NOT a Solidity compiler, or a substitute for one! It only parses a Solidity-like syntax to generate Rust types, designed for simple interfaces defined inline with your other Rust code.
Further, this macro does not resolve imports or dependencies, and it does not handle inheritance. All required types must be provided in the same macro scope.
§Attributes
Inner attributes (#![...]
) are parsed at the top of the input, just like a
Rust module. These can only be sol
attributes, and they will apply to the
entire input.
Outer attributes (#[...]
) are parsed as part of each individual item, like
structs, enums, etc. These can be any Rust attribute, and they will be added
to every Rust item generated from the Solidity item.
This macro provides the sol
attribute, which can be used to customize the
generated code. Note that unused attributes are currently silently ignored,
but this may change in the future.
Note that the sol
attribute does not compose like other Rust attributes, for example
#[cfg_attr]
will NOT work, as it is parsed and extracted from the input separately.
This is a limitation of the proc-macro API.
List of all #[sol(...)]
supported values:
-
rpc [ = <bool = false>]
(contracts and alike only): generates a structs with methods to constructeth_call
s to an on-chain contract through Ethereum JSON RPC, similar to the default behavior ofabigen
. This makes use of thealloy-contract
crate.N.B: at the time of writing, the
alloy-contract
crate is not yet released oncrates.io
, and its API is completely unstable and subject to change, so this feature is not yet recommended for use.Generates the following items inside of the
{contract_name}
module:struct {contract_name}Instance<P: Provider> { ... }
pub fn new(...) -> {contract_name}Instance<P>
+ getters and setterspub fn call_builder<C: SolCall>(&self, call: &C) -> SolCallBuilder<P, C>
, as a generic way to call any function of the contract, even if not generated by the macro; prefer the other methods when possiblepub fn <functionName>(&self, <parameters>...) -> CallBuilder<P, functionReturn>
for each function in the contractpub fn <eventName>_filter(&self) -> Event<P, eventName>
for each event in the contract
pub fn new ...
, same as above just as a free function in the contract module
-
abi [ = <bool = false>]
: generates functions which return the dynamic ABI representation (provided byalloy_json_abi
) of all the generated items. Requires the"json"
feature. For:- contracts: generates an
abi
module nested inside of the contract module, which contains:pub fn contract() -> JsonAbi
,pub fn constructor() -> Option<Constructor>
pub fn fallback() -> Option<Fallback>
pub fn receive() -> Option<Receive>
pub fn functions() -> BTreeMap<String, Vec<Function>>
pub fn events() -> BTreeMap<String, Vec<Event>>
pub fn errors() -> BTreeMap<String, Vec<Error>>
- items: generates implementations of the
SolAbiExt
trait, alongside the existingalloy-sol-types
traits
- contracts: generates an
-
alloy_sol_types = <path = ::alloy_sol_types>
(inner attribute only): specifies the path to the required dependencyalloy-sol-types
. -
alloy_contract = <path = ::alloy_contract>
(inner attribute only): specifies the path to the optional dependency [alloy-contract
]. This is only used by therpc
attribute. -
all_derives [ = <bool = false>]
: adds all possible#[derive(...)]
attributes to all generated types. May significantly increase compile times due to all the extra generated code. This is the default behavior ofabigen
-
extra_methods [ = <bool = false>]
: adds extra implementations and methods to all applicable generated types, such asFrom
impls andas_<variant>
methods. May significantly increase compile times due to all the extra generated code. This is the default behavior ofabigen
-
docs [ = <bool = true>]
: adds doc comments to all generated types. This is the default behavior ofabigen
-
bytecode = <hex string literal>
(contract-like only): specifies the creation/init bytecode of a contract. This will emit astatic
item with the specified bytes. -
deployed_bytecode = <hex string literal>
(contract-like only): specifies the deployed bytecode of a contract. This will emit astatic
item with the specified bytes. -
type_check = <string literal>
(UDVT only): specifies a function to be used to check an User Defined Type.
§Structs and enums
Structs and enums generate their corresponding Rust types. Enums are
additionally annotated with #[repr(u8)]
, and as such can have a maximum of
256 variants.
use alloy_primitives::{hex, Address, U256};
use alloy_sol_types::{sol, SolEnum, SolType};
sol! {
struct Foo {
uint256 bar;
address[] baz;
}
/// Nested struct.
struct Nested {
Foo[2] a;
address b;
}
enum Enum {
A,
B,
C,
}
}
#[test]
fn structs() {
let my_foo = Foo {
bar: U256::from(42),
baz: vec![Address::repeat_byte(0x11), Address::repeat_byte(0x22)],
};
let _nested = Nested { a: [my_foo.clone(), my_foo.clone()], b: Address::ZERO };
let abi_encoded = Foo::abi_encode_sequence(&my_foo);
assert_eq!(
abi_encoded,
hex! {
"000000000000000000000000000000000000000000000000000000000000002a" // bar
"0000000000000000000000000000000000000000000000000000000000000040" // baz offset
"0000000000000000000000000000000000000000000000000000000000000002" // baz length
"0000000000000000000000001111111111111111111111111111111111111111" // baz[0]
"0000000000000000000000002222222222222222222222222222222222222222" // baz[1]
}
);
let abi_encoded_enum = Enum::B.abi_encode();
assert_eq!(
abi_encoded_enum,
hex! {
"0000000000000000000000000000000000000000000000000000000000000001"
}
);
}
§UDVT and type aliases
User defined value types (UDVT) generate a tuple struct with the type as its only field, and type aliases simply expand to the corresponding Rust type.
use alloy_primitives::Address;
use alloy_sol_types::{sol, SolType};
// Type definition: generates a new struct that implements `SolType`
sol! {
type MyType is uint256;
}
// Type aliases
type B32 = sol! { bytes32 };
// This is equivalent to the following:
// type B32 = alloy_sol_types::sol_data::Bytes<32>;
type SolArrayOf<T> = sol! { T[] };
type SolTuple = sol! { tuple(address, bytes, string) };
#[test]
fn types() {
let _ = <sol!(bool)>::abi_encode(&true);
let _ = B32::abi_encode(&[0; 32]);
let _ = SolArrayOf::<sol!(bool)>::abi_encode(&vec![true, false]);
let _ = SolTuple::abi_encode(&(Address::ZERO, vec![0; 32], "hello".to_string()));
}
§State variables
Public and external state variables will generate a getter function just like in Solidity.
See the functions and contracts sections for more information.
§Functions and errors
Functions generate two structs that implement SolCall
: <name>Call
for
the function arguments, and <name>Return
for the return values.
In the case of overloaded functions, an underscore and the index of the
function will be appended to <name>
(like foo_0
, foo_1
…) for
disambiguation, but the signature will remain the same.
E.g. if there are two functions named foo
, the generated types will be
foo_0Call
and foo_1Call
, each of which will implement SolCall
with their respective signatures.
use alloy_primitives::{hex, keccak256, U256};
use alloy_sol_types::{sol, SolCall, SolError};
sol! {
function foo(uint256 a, uint256 b) external view returns (uint256);
// These will generate structs prefixed with `overloaded_0`, `overloaded_1`,
// and `overloaded_2` by default, but each signature is calculated with
// `overloaded` as the function name.
function overloaded();
function overloaded(uint256) returns (uint256);
function overloaded(string);
// State variables will generate getter functions just like in Solidity.
mapping(uint k => bool v) public variableGetter;
/// Implements [`SolError`].
#[derive(Debug, PartialEq, Eq)]
error MyError(uint256 a, uint256 b);
}
#[test]
fn function() {
assert_call_signature::<fooCall>("foo(uint256,uint256)");
let call = fooCall { a: U256::from(1), b: U256::from(2) };
let _call_data = call.abi_encode();
let _ = overloaded_0Call {};
assert_call_signature::<overloaded_0Call>("overloaded()");
let _ = overloaded_1Call { _0: U256::from(1) };
assert_call_signature::<overloaded_1Call>("overloaded(uint256)");
let _ = overloaded_2Call { _0: "hello".into() };
assert_call_signature::<overloaded_2Call>("overloaded(string)");
// Exactly the same as `function variableGetter(uint256) returns (bool)`.
let _ = variableGetterCall { k: U256::from(2) };
assert_call_signature::<variableGetterCall>("variableGetter(uint256)");
let _ = variableGetterReturn { v: false };
}
#[test]
fn error() {
assert_error_signature::<MyError>("MyError(uint256,uint256)");
let call_data = hex!(
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000002"
);
assert_eq!(
MyError::abi_decode_raw(&call_data, true),
Ok(MyError { a: U256::from(1), b: U256::from(2) })
);
}
fn assert_call_signature<T: SolCall>(expected: &str) {
assert_eq!(T::SIGNATURE, expected);
assert_eq!(T::SELECTOR, keccak256(expected)[..4]);
}
fn assert_error_signature<T: SolError>(expected: &str) {
assert_eq!(T::SIGNATURE, expected);
assert_eq!(T::SELECTOR, keccak256(expected)[..4]);
}
§Events
Events generate a struct that implements SolEvent
.
Note that events have special encoding rules in Solidity. For example,
string indexed
will be encoded in the topics as its bytes32
Keccak-256
hash, and as such the generated field for this argument will be bytes32
,
and not string
.
#![allow(clippy::assertions_on_constants)]
use alloy_primitives::{hex, keccak256, Bytes, Log, B256, U256};
use alloy_rlp::{Decodable, Encodable};
use alloy_sol_types::{abi::token::WordToken, sol, SolEvent};
sol! {
#[derive(Debug, Default, PartialEq)]
event MyEvent(bytes32 indexed a, uint256 b, string indexed c, bytes d);
event LogNote(
bytes4 indexed sig,
address indexed guy,
bytes32 indexed foo,
bytes32 indexed bar,
uint wad,
bytes fax
) anonymous;
struct Data {
bytes data;
}
event MyEvent2(Data indexed data);
}
#[test]
fn event() {
assert_event_signature::<MyEvent>("MyEvent(bytes32,uint256,string,bytes)");
assert!(!MyEvent::ANONYMOUS);
let event = MyEvent {
a: [0x11; 32].into(),
b: U256::from(1u64),
c: keccak256("Hello World"),
d: Bytes::default(),
};
// topics are `(SELECTOR, a, keccak256(c))`
assert_eq!(
event.encode_topics_array::<3>(),
[
WordToken(MyEvent::SIGNATURE_HASH),
WordToken(B256::repeat_byte(0x11)),
WordToken(keccak256("Hello World"))
]
);
// dynamic data is `abi.abi_encode(b, d)`
assert_eq!(
event.encode_data(),
hex!(
// b
"0000000000000000000000000000000000000000000000000000000000000001"
// d offset
"0000000000000000000000000000000000000000000000000000000000000040"
// d length
"0000000000000000000000000000000000000000000000000000000000000000"
),
);
assert_event_signature::<LogNote>("LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)");
assert!(LogNote::ANONYMOUS);
assert_event_signature::<MyEvent2>("MyEvent2((bytes))");
assert!(!MyEvent2::ANONYMOUS);
}
#[test]
fn event_rlp_roundtrip() {
let event = MyEvent {
a: [0x11; 32].into(),
b: U256::from(1u64),
c: keccak256("Hello World"),
d: Vec::new().into(),
};
let rlpable_log = Log::<MyEvent>::new_from_event_unchecked(Default::default(), event);
let mut rlp_encoded = vec![];
rlpable_log.encode(&mut rlp_encoded);
assert_eq!(rlpable_log.length(), rlp_encoded.len());
let rlp_decoded = Log::decode(&mut rlp_encoded.as_slice()).unwrap();
assert_eq!(rlp_decoded, rlpable_log.reserialize());
let decoded_log = MyEvent::decode_log(&rlp_decoded, true).unwrap();
assert_eq!(decoded_log, rlpable_log)
}
fn assert_event_signature<T: SolEvent>(expected: &str) {
assert_eq!(T::SIGNATURE, expected);
assert_eq!(T::SIGNATURE_HASH, keccak256(expected));
}
§Contracts/interfaces
Contracts generate a module with the same name, which contains all the items.
This module will also contain 3 container enums which implement SolInterface
, one for each:
- functions:
<contract_name>Calls
- errors:
<contract_name>Errors
- events:
<contract_name>Events
Note that by default only ABI encoding are generated. In order to generate bindings for RPC calls, you must enable the#[sol(rpc)]
attribute.
use alloy_primitives::{address, hex, U256};
use alloy_sol_types::{sol, SolCall, SolConstructor, SolInterface};
sol! {
/// Interface of the ERC20 standard as defined in [the EIP].
///
/// [the EIP]: https://eips.ethereum.org/EIPS/eip-20
#[derive(Debug, PartialEq, Eq)]
contract ERC20 {
mapping(address account => uint256) public balanceOf;
constructor(string name, string symbol);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
}
#[test]
fn constructor() {
let constructor_args =
ERC20::constructorCall::new((String::from("Wrapped Ether"), String::from("WETH")))
.abi_encode();
let constructor_args_expected = hex!("00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d577261707065642045746865720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045745544800000000000000000000000000000000000000000000000000000000");
assert_eq!(constructor_args.as_slice(), constructor_args_expected);
}
#[test]
fn transfer() {
// random mainnet ERC20 transfer
// https://etherscan.io/tx/0x947332ff624b5092fb92e8f02cdbb8a50314e861a4b39c29a286b3b75432165e
let data = hex!(
"a9059cbb"
"0000000000000000000000008bc47be1e3abbaba182069c89d08a61fa6c2b292"
"0000000000000000000000000000000000000000000000000000000253c51700"
);
let expected = ERC20::transferCall {
to: address!("8bc47be1e3abbaba182069c89d08a61fa6c2b292"),
amount: U256::from(9995360000_u64),
};
assert_eq!(data[..4], ERC20::transferCall::SELECTOR);
let decoded = ERC20::ERC20Calls::abi_decode(&data, true).unwrap();
assert_eq!(decoded, ERC20::ERC20Calls::transfer(expected));
assert_eq!(decoded.abi_encode(), data);
}
§JSON ABI
Contracts can also be generated from ABI JSON strings and files, similar to
the ethers-rs abigen!
macro.
JSON objects containing the abi
, evm
, bytecode
, deployedBytecode
,
and similar keys are also supported.
Note that only valid JSON is supported, and not the human-readable ABI
format, also used by abigen!
. This should instead be easily converted to
normal Solidity input.
Prefer using Solidity input when possible, as the JSON ABI format omits some information which is useful to this macro, such as enum variants and visibility modifiers on functions.
use alloy_sol_types::{sol, SolCall};
sol!(
MyJsonContract1,
r#"[
{
"inputs": [
{ "name": "bar", "type": "uint256" },
{
"internalType": "struct MyStruct",
"name": "baz",
"type": "tuple",
"components": [
{ "name": "a", "type": "bool[]" },
{ "name": "b", "type": "bytes18[][]" }
]
}
],
"outputs": [],
"stateMutability": "view",
"name": "foo",
"type": "function"
}
]"#
);
// This is the same as:
sol! {
interface MyJsonContract2 {
struct MyStruct {
bool[] a;
bytes18[][] b;
}
function foo(uint256 bar, MyStruct baz) external view;
}
}
#[test]
fn abigen() {
assert_eq!(MyJsonContract1::fooCall::SIGNATURE, MyJsonContract2::fooCall::SIGNATURE);
}