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(())
}