An example that uses the ERC20 interface's balanceOf
method.
To get started, you need to have Rust installed. If you haven't done so, follow the instructions here.
Next, you will also need to have the cargo-risczero
tool installed following the instructions here.
You'll also need access to an Ethereum Sepolia RPC endpoint. You can for example use ethereum-sepolia-rpc.publicnode.com or a commercial RPC provider like Alchemy.
To run the example, which queries the USDT balance of 0x9737100D2F42a196DE56ED0d1f6fF598a250E7E4
on Sepolia, execute the following command:
RPC_URL=https://ethereum-sepolia-rpc.publicnode.com RUST_LOG=info cargo run --release
The output should resemble the following:
2024-08-05T17:29:56.020271Z INFO risc0_steel::host: Environment initialized for block 6442962
2024-08-05T17:29:56.020499Z INFO risc0_steel: Commitment to block 0x9905cc8a33f5705b3ab32f9fcf5dca433a7b172695943e0df285dde98530d7e7
2024-08-05T17:29:56.021170Z INFO risc0_steel::contract: Executing preflight calling 'balanceOf(address)' on 0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0
Call balanceOf(address) Function by 0xf08A…1715 on 0xaA8E…33D0 returns: 399534748753251
Running the guest with the constructed input...
View call result: 399534748753251
2024-08-05T17:29:58.002719Z INFO executor: risc0_zkvm::host::server::exec::executor: execution time: 115.07675ms
2024-08-05T17:29:58.002737Z INFO executor: risc0_zkvm::host::server::session: number of segments: 6
2024-08-05T17:29:58.002740Z INFO executor: risc0_zkvm::host::server::session: total cycles: 5767168
2024-08-05T17:29:58.002742Z INFO executor: risc0_zkvm::host::server::session: user cycles: 4307081
Here is a snippet of the relevant code of the guest:
/// Specify the function to call using the [`sol!`] macro.
/// This parses the Solidity syntax to generate a struct that implements the `SolCall` trait.
sol! {
/// ERC-20 balance function signature.
interface IERC20 {
function balanceOf(address account) external view returns (uint);
}
}
/// Function to call, implements the `SolCall` trait.
const CALL: IERC20::balanceOfCall = IERC20::balanceOfCall {
account: address!("9737100D2F42a196DE56ED0d1f6fF598a250E7E4"),
};
/// Address of the deployed contract to call the function on (USDT contract on Sepolia).
const CONTRACT: Address = address!("aA8E23Fb1079EA71e0a56F48a2aA51851D8433D0");
/// Address of the caller. If not provided, the caller will be the [CONTRACT].
const CALLER: Address = address!("f08A50178dfcDe18524640EA6618a1f965821715");
fn main() {
// Read the input from the guest environment.
let input: EthEvmInput = env::read();
// Converts the input into a `EvmEnv` for execution. The `with_chain_spec` method is used
// to specify the chain configuration. It checks that the state matches the state root in the
// header provided in the input.
let env = input.into_env().with_chain_spec(Ð_SEPOLIA_CHAIN_SPEC);
// Commit the block hash and number used when deriving `EvmEnv` to the journal.
env::commit_slice(&env.commitment().abi_encode());
// Execute the view call; it returns the result in the type generated by the `sol!` macro.
let contract = Contract::new(CONTRACT, &env);
let returns = contract.call_builder(&CALL).from(CALLER).call().unwrap();
println!("View call result: {}", returns._0);
}
Here is a snippet to the relevant code on the host, it requires the same arguments as the guest:
// Create an EVM environment from an RPC endpoint and a block number or tag.
let mut env = EthEvmEnv::from_rpc(args.rpc_url, BlockNumberOrTag::Latest).await?;
// The `with_chain_spec` method is used to specify the chain configuration.
env = env.with_chain_spec(Ð_SEPOLIA_CHAIN_SPEC);
// Preflight the call to prepare the input that is required to execute the function in
// the guest without RPC access. It also returns the result of the call.
let mut contract = Contract::preflight(CONTRACT, &mut env);
let returns = contract.call_builder(&CALL).from(CALLER).call().await?;
// Finally, construct the input from the environment.
let input = env.into_input().await?;