Skip to content

Using the sol! macro

Alloy provides a powerful and intuitive way to read and write to contracts using the sol! procedural macro.

The sol! 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! { 
    // ... with attributes too!
    #[derive(Debug)] 
    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:#?}"); 

Generate Rust Bindings

The sol! macro comes with the flexibilty of generating rust bindings for your contracts in multiple ways.

Solidity

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"
);

JSON-ABI

By enabling the json feature, you can generate rust bindings from ABI compliant strings or abi files directly.

The format is either a JSON ABI array or an object containing an "abi" key. It supports common artifact formats like Foundry's:

Using an ABI file:

sol!(
    ICounter,
    "abi/Counter.json"
);

Using an ABI compliant string:

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"
        }
   ]"#
);

Snippets

At times, you don't want to generate for the complete contract to keep your project light but still want call one or two functions. You can generate bindings for valid solidity snippets as well.

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);

Attributes

One can use the sol attributes to add additional functionality to the generated rust code.

For example, the #[sol(rpc)] attribute generates code to enable seamless interaction with the contract over the RPC provider. It 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 of deploying a contract from Solidity code using the `sol!` macro to Anvil and
//! 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().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?;
 
    println!("Retrieved number: {number}");
 
    Ok(())
}

You can find a full list of attributes here.