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! {
    struct Foo {
        uint256 bar;
        bool baz;
    }
}

// A corresponding Rust struct is generated!

// pub struct Foo {
//     pub bar: Uint<256, 4>,
//     pub baz: bool,
// }

let foo = Foo { bar: U256::from(42), baz: true };

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!(
    #[sol(rpc)]
    Counter,
    "artifacts/Counter.json"
);

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:

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.

//! Example showing how to use the `#[sol(rpc)]` and #[sol(bytecode = "0x...")] attributes
//! interacting with it.

use alloy::{
    network::EthereumWallet, node_bindings::Anvil, primitives::U256, providers::ProviderBuilder,
    signers::local::PrivateKeySigner, sol,
};
use eyre::Result;

// Codegen from embedded Solidity code and precompiled bytecode.
sol! {
    #[allow(missing_docs)]
    // solc v0.8.26; solc a.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 anvil = Anvil::new().try_spawn()?;

    // Set up signer from the first default Anvil account (Alice).
    let signer: PrivateKeySigner = anvil.keys()[0].clone().into();
    let wallet = EthereumWallet::from(signer);

    // Create a provider with the wallet.
    let rpc_url = anvil.endpoint().parse()?;
    let provider =
        ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);

    println!("Anvil running at `{}`", anvil.endpoint());

    // Deploy the 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(())
}