The sol!
procedural macro
The sol!
procedural macro parses Solidity syntax to generate types that implement alloy-sol-types traits. It uses
syn-solidity
, a syn-powered Solidity parser. It aims to mimic the behavior of the official Solidity compiler (solc
) when it comes to parsing valid Solidity code. This means that all valid Solidity code, as recognized by solc
0.5.0
and above is supported.
In its most basic form sol!
is used like this:
use alloy::{primitives::U256, sol};
// Declare a Solidity type in standard Solidity
sol! {
#[derive(Debug)] // ... with attributes too!
struct Foo {
uint256 bar;
bool baz;
}
}
// A corresponding Rust struct is generated:
// #[derive(Debug)]
// pub struct Foo {
// pub bar: U256,
// pub baz: bool,
// }
let foo = Foo { bar: U256::from(42), baz: true };
println!("{foo:#?}");
Usage
There are multiple ways to use the sol!
macro.
You can directly write Solidity code:
sol! {
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
Or provide a path to a Solidity file:
sol!(
"artifacts/Counter.sol"
);
Alternatively, if you enable the json
feature flag, you can provide an ABI, or a path to one, in JSON format:
sol!(
ICounter,
r#"[
{
"type": "function",
"name": "increment",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "number",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "setNumber",
"inputs": [
{
"name": "newNumber",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]"#
);
This is the same as:
sol! {
interface ICounter {
uint256 public number;
function setNumber(uint256 newNumber);
function increment();
}
}
Alternatively, you can load an ABI by file; the format is either a JSON ABI array
or an object containing an "abi"
key. It supports common artifact formats like Foundry’s:
sol!(
ICounter,
"abi/Counter.json"
);
You can also use functions directly:
sol!(
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
);
println!("Decoding https://etherscan.io/tx/0xd1b449d8b1552156957309bffb988924569de34fbf21b51e7af31070cc80fe9a");
let input = hex::decode("0x38ed173900000000000000000000000000000000000000000001a717cc0a3e4f84c00000000000000000000000000000000000000000000000000000000000000283568400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000201f129111c60401630932d9f9811bd5b5fff34e000000000000000000000000000000000000000000000000000000006227723d000000000000000000000000000000000000000000000000000000000000000200000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7")?;
// Decode the input using the generated `swapExactTokensForTokens` bindings.
let decoded = swapExactTokensForTokensCall::abi_decode(&input, false);
Attributes
Combined with the sol!
macro’s #[sol(rpc)]
attribute,
CallBuilder
can be used to interact with
on-chain contracts. The #[sol(rpc)]
attribute generates a method for each function in a contract
that returns a CallBuilder
for that function.
If #[sol(bytecode = "0x...")]
is provided, the contract can be deployed with Counter::deploy
and a new instance will be created.
The bytecode is also loaded from Foundry-style JSON artifact files.
//! Example showing how to use the `#[sol(rpc)]` and #[sol(bytecode = "0x...")] attributes
//! interacting with it.
use alloy::{primitives::U256, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
sol! {
#[allow(missing_docs)]
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
#[sol(rpc, bytecode="6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract.
let contract = Counter::deploy(&provider).await?;
println!("Deployed contract at address: {}", contract.address());
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
let number = builder.call().await?.number.to_string();
println!("Retrieved number: {number}");
Ok(())
}