diff --git a/README.md b/README.md index d6636f05..cd913183 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,10 @@ cargo run --example mnemonic_signer - [x] [Creating Instances](./examples/big-numbers/examples/create_instances.rs) - [x] [Math operations](./examples/big-numbers/examples/math_operations.rs) - [x] [Math utilities](./examples/big-numbers/examples/math_utilities.rs) -- [ ] Contracts - - [ ] Abigen - - [ ] Compile - - [ ] Creating Instances - - [ ] Deploy Anvil - - [ ] Deploy from ABI and bytecode - - [ ] Deploy Moonbeam - - [ ] Events - - [ ] Events with meta - - [ ] Methods +- [x] Contracts + - [x] [Deploy from artifact](./examples/contracts/examples/deploy_from_artifact.rs) + - [x] [Deploy from contract](./examples/contracts/examples/deploy_from_contract.rs) + - [x] [Generate](./examples/contracts/examples/generate.rs) - [ ] Events - [ ] Logs and filtering - [ ] Solidity topics diff --git a/examples/contracts/Cargo.toml b/examples/contracts/Cargo.toml new file mode 100644 index 00000000..4dbe196e --- /dev/null +++ b/examples/contracts/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "examples-contracts" + +publish.workspace = true +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dev-dependencies] +alloy.workspace = true + +eyre.workspace = true +reqwest.workspace = true +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/examples/contracts/examples/abi/Counter.json b/examples/contracts/examples/abi/Counter.json new file mode 100644 index 00000000..b6c39c5f --- /dev/null +++ b/examples/contracts/examples/abi/Counter.json @@ -0,0 +1,35 @@ +[ + { + "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" + } +] \ No newline at end of file diff --git a/examples/contracts/examples/abi/IERC20.json b/examples/contracts/examples/abi/IERC20.json new file mode 100644 index 00000000..37f31f97 --- /dev/null +++ b/examples/contracts/examples/abi/IERC20.json @@ -0,0 +1,279 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "guy", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "src", + "type": "address" + }, + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "wad", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "guy", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } +] \ No newline at end of file diff --git a/examples/contracts/examples/artifacts/Counter.json b/examples/contracts/examples/artifacts/Counter.json new file mode 100644 index 00000000..3c90354e --- /dev/null +++ b/examples/contracts/examples/artifacts/Counter.json @@ -0,0 +1,134 @@ +{ + "abi": [ + { + "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" + } + ], + "bytecode": { + "object": "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220f90c910068fe53f64c8ae6b87a01287f43bbafbff9a3bceded7c441b71ceb3c364736f6c63430008180033", + "sourceMap": "65:192:22:-:0;;;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220f90c910068fe53f64c8ae6b87a01287f43bbafbff9a3bceded7c441b71ceb3c364736f6c63430008180033", + "sourceMap": "65:192:22:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;116:80;;;;;;:::i;:::-;171:6;:18;116:80;;;88:21;;;;;;;;;345:25:24;;;333:2;318:18;88:21:22;;;;;;;202:53;;240:6;:8;;;:6;:8;;;:::i;:::-;;;;;;202:53::o;14:180:24:-;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;-1:-1:-1;165:23:24;;14:180;-1:-1:-1;14:180:24:o;381:232::-;420:3;441:17;;;438:140;;500:10;495:3;491:20;488:1;481:31;535:4;532:1;525:15;563:4;560:1;553:15;438:140;-1:-1:-1;605:1:24;594:13;;381:232::o", + "linkReferences": {} + }, + "methodIdentifiers": { + "increment()": "d09de08a", + "number()": "8381f58a", + "setNumber(uint256)": "3fb5c1cb" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"increment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"number\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newNumber\",\"type\":\"uint256\"}],\"name\":\"setNumber\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/Counter.sol\":\"Counter\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/Counter.sol\":{\"keccak256\":\"0x09277f949d59a9521708c870dc39c2c434ad8f86a5472efda6a732ef728c0053\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://94cd5258357da018bf911aeda60ed9f5b130dce27445669ee200313cd3389200\",\"dweb:/ipfs/QmNbEfWAqXCtfQpk6u7TpGa8sTHXFLpUz7uebz2FVbchSC\"]}},\"version\":1}", + "metadata": { + "compiler": { + "version": "0.8.24+commit.e11b9ed9" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "increment" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "number", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "setNumber" + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "remappings": [ + "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src/" + ], + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "compilationTarget": { + "src/Counter.sol": "Counter" + }, + "evmVersion": "paris", + "libraries": {} + }, + "sources": { + "src/Counter.sol": { + "keccak256": "0x09277f949d59a9521708c870dc39c2c434ad8f86a5472efda6a732ef728c0053", + "urls": [ + "bzz-raw://94cd5258357da018bf911aeda60ed9f5b130dce27445669ee200313cd3389200", + "dweb:/ipfs/QmNbEfWAqXCtfQpk6u7TpGa8sTHXFLpUz7uebz2FVbchSC" + ], + "license": "UNLICENSED" + } + }, + "version": 1 + }, + "id": 22 +} \ No newline at end of file diff --git a/examples/contracts/examples/deploy_from_artifact.rs b/examples/contracts/examples/deploy_from_artifact.rs new file mode 100644 index 00000000..8bf1ad80 --- /dev/null +++ b/examples/contracts/examples/deploy_from_artifact.rs @@ -0,0 +1,70 @@ +//! Example of deploying a contract from an artifact to Anvil and interacting with it. + +use alloy::{ + network::EthereumSigner, + node_bindings::Anvil, + primitives::U256, + providers::{Provider, ProviderBuilder}, + rpc::client::RpcClient, + signers::wallet::LocalWallet, + sol, +}; +use eyre::Result; + +// Codegen from artifact. +sol!( + #[sol(rpc)] + Counter, + "examples/artifacts/Counter.json" +); + +#[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 wallet + let wallet: LocalWallet = anvil.keys()[0].clone().into(); + + // Create a provider with a signer. + let http = anvil.endpoint().parse()?; + let provider = ProviderBuilder::new() + .signer(EthereumSigner::from(wallet)) + .on_client(RpcClient::new_http(http)); + + println!("Anvil running at `{}`", anvil.endpoint()); + + // Get the base fee for the block. + let base_fee = provider.get_gas_price().await?; + + // Deploy the contract. + let contract_builder = Counter::deploy_builder(&provider); + let estimate = contract_builder.estimate_gas().await?; + let contract_address = + contract_builder.gas(estimate).gas_price(base_fee).nonce(0).deploy().await?; + + println!("Deployed contract at address: {:?}", contract_address); + + let contract = Counter::new(contract_address, &provider); + + let estimate = contract.setNumber(U256::from(42)).estimate_gas().await?; + let builder = contract.setNumber(U256::from(42)).nonce(1).gas(estimate).gas_price(base_fee); + let receipt = builder.send().await?.get_receipt().await?; + + println!("Set number to 42: {:?}", receipt.transaction_hash); + + // Increment the number to 43. + let estimate = contract.increment().estimate_gas().await?; + let builder = contract.increment().nonce(2).gas(estimate).gas_price(base_fee); + let receipt = builder.send().await?.get_receipt().await?; + + println!("Incremented number: {:?}", receipt.transaction_hash); + + // Retrieve the number, which should be 43. + let Counter::numberReturn { _0 } = contract.number().call().await?; + + println!("Retrieved number: {:?}", _0.to_string()); + + Ok(()) +} diff --git a/examples/contracts/examples/deploy_from_contract.rs b/examples/contracts/examples/deploy_from_contract.rs new file mode 100644 index 00000000..6853aded --- /dev/null +++ b/examples/contracts/examples/deploy_from_contract.rs @@ -0,0 +1,80 @@ +//! Example of deploying a contract from Solidity code to Anvil and interacting with it. + +use alloy::{ + network::EthereumSigner, + node_bindings::Anvil, + primitives::U256, + providers::{Provider, ProviderBuilder}, + rpc::client::RpcClient, + signers::wallet::LocalWallet, + sol, +}; +use eyre::Result; + +// Codegen from embedded Solidity code and precompiled bytecode. +sol! { + // solc v0.8.24; solc a.sol --via-ir --optimize --bin + #[sol(rpc, bytecode="608080604052346100155760d2908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c9081633fb5c1cb1460865781638381f58a14606f575063d09de08a146039575f80fd5b34606b575f366003190112606b575f545f1981146057576001015f55005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b34606b575f366003190112606b576020905f548152f35b34606b576020366003190112606b576004355f5500fea2646970667358221220bdecd3c1dd631eb40587cafcd6e8297479db76db6a328e18ad1ea5b340852e3864736f6c63430008180033")] + 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 wallet + let wallet: LocalWallet = anvil.keys()[0].clone().into(); + + // Create a provider with a signer. + let http = anvil.endpoint().parse()?; + let provider = ProviderBuilder::new() + .signer(EthereumSigner::from(wallet)) + .on_client(RpcClient::new_http(http)); + + println!("Anvil running at `{}`", anvil.endpoint()); + + // Get the base fee for the block. + let base_fee = provider.get_gas_price().await?; + + // Deploy the contract. + let contract_builder = Counter::deploy_builder(&provider); + let estimate = contract_builder.estimate_gas().await?; + let contract_address = + contract_builder.gas(estimate).gas_price(base_fee).nonce(0).deploy().await?; + + println!("Deployed contract at address: {:?}", contract_address); + + let contract = Counter::new(contract_address, &provider); + + let estimate = contract.setNumber(U256::from(42)).estimate_gas().await?; + let builder = contract.setNumber(U256::from(42)).nonce(1).gas(estimate).gas_price(base_fee); + let receipt = builder.send().await?.get_receipt().await?; + + println!("Set number to 42: {:?}", receipt.transaction_hash); + + // Increment the number to 43. + let estimate = contract.increment().estimate_gas().await?; + let builder = contract.increment().nonce(2).gas(estimate).gas_price(base_fee); + let receipt = builder.send().await?.get_receipt().await?; + + println!("Incremented number: {:?}", receipt.transaction_hash); + + // Retrieve the number, which should be 43. + let Counter::numberReturn { _0 } = contract.number().call().await?; + + println!("Retrieved number: {:?}", _0.to_string()); + + Ok(()) +} diff --git a/examples/contracts/examples/generate.rs b/examples/contracts/examples/generate.rs new file mode 100644 index 00000000..b5ef21fc --- /dev/null +++ b/examples/contracts/examples/generate.rs @@ -0,0 +1,32 @@ +//! Example of generating code from ABI file to interact with the contract. + +use alloy::{node_bindings::Anvil, providers::ProviderBuilder, rpc::client::RpcClient, sol}; +use eyre::Result; + +// Codegen from ABI file to interact with the contract. +sol!( + #[sol(rpc)] + IERC20, + "examples/abi/IERC20.json" +); + +#[tokio::main] +async fn main() -> Result<()> { + // Spin up a forked Anvil node. + // Ensure `anvil` is available in $PATH + let anvil = Anvil::new().fork("https://eth.llamarpc.com").try_spawn()?; + + // Create a provider. + let provider = + ProviderBuilder::<_>::new().on_client(RpcClient::new_http(anvil.endpoint().parse()?)); + + // Create a contract instance. + let contract = IERC20::new("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse()?, provider); + + // Call the contract, retrieve the total supply. + let IERC20::totalSupplyReturn { _0 } = contract.totalSupply().call().await?; + + println!("WETH total supply is {_0}"); + + Ok(()) +}