diff --git a/Cargo.toml b/Cargo.toml index a421ed3d..f75d10dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,3 +101,4 @@ tokio = "1" # misc eyre = "0.6.12" +serde_json = "1.0.117" diff --git a/README.md b/README.md index bc266c84..468740e7 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,12 @@ This repository contains the following examples: - [x] [Math operations](./examples/big-numbers/examples/math_operations.rs) - [x] [Math utilities](./examples/big-numbers/examples/math_utilities.rs) - [x] Contracts + - [x] [Interact using contract instance](./examples/contracts/examples/contract_instance.rs) - [x] [Deploy from artifact](./examples/contracts/examples/deploy_from_artifact.rs) - [x] [Deploy from bytecode](./examples/contracts/examples/deploy_from_bytecode.rs) - [x] [Deploy from contract](./examples/contracts/examples/deploy_from_contract.rs) - [x] [Interact with ABI](./examples/contracts/examples/interact_with_abi.rs) + - [x] [DynSol return values](./examples/contracts/examples/dyn_sol_struct_tuples.rs) - [x] Fillers (Middleware) - [x] [Gas estimation filler](./examples/fillers/examples/gas_filler.rs) - [x] [Nonce management filler](./examples/fillers/examples/nonce_filler.rs) diff --git a/examples/contracts/Cargo.toml b/examples/contracts/Cargo.toml index d02f79ea..1e7b70fa 100644 --- a/examples/contracts/Cargo.toml +++ b/examples/contracts/Cargo.toml @@ -17,3 +17,4 @@ alloy.workspace = true eyre.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +serde_json.workspace = true diff --git a/examples/contracts/examples/abi/Colors.json b/examples/contracts/examples/abi/Colors.json new file mode 100644 index 00000000..2fadf66a --- /dev/null +++ b/examples/contracts/examples/abi/Colors.json @@ -0,0 +1,119 @@ +[ + { + "type": "function", + "name": "colors", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "r", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "g", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "b", + "type": "uint8", + "internalType": "uint8" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getColor", + "inputs": [ + { + "name": "user", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct Colors.Color", + "components": [ + { + "name": "r", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "g", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "b", + "type": "uint8", + "internalType": "uint8" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getColorAsTuple", + "inputs": [ + { + "name": "user", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "", + "type": "uint8", + "internalType": "uint8" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setColor", + "inputs": [ + { + "name": "r", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "g", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "b", + "type": "uint8", + "internalType": "uint8" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] \ No newline at end of file diff --git a/examples/contracts/examples/contract_instance.rs b/examples/contracts/examples/contract_instance.rs new file mode 100644 index 00000000..90b58bee --- /dev/null +++ b/examples/contracts/examples/contract_instance.rs @@ -0,0 +1,98 @@ +//! This example demonstrates how to interact with a contract that is already deployed onchain using +//! the `ContractInstance` interface. +use alloy::{ + contract::{ContractInstance, Interface}, + dyn_abi::DynSolValue, + network::{Ethereum, TransactionBuilder}, + primitives::{hex, U256}, + providers::{Provider, ProviderBuilder}, + rpc::types::TransactionRequest, + transports::http::{Client, Http}, +}; +use eyre::Result; + +/** +* solc v0.8.26; solc a.sol --via-ir --optimize --bin +* contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } + } +* +*/ +#[tokio::main] +async fn main() -> Result<()> { + let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); + + // Deploy the Counter contract + let bytecode = hex::decode("6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")?; + let deploy_tx = TransactionRequest::default().with_deploy_code(bytecode); + + let contract_address = + provider.send_transaction(deploy_tx).await?.get_receipt().await?.contract_address.unwrap(); + + // Get the contract abi + let path = std::env::current_dir()?.join("examples/contracts/examples/artifacts/Counter.json"); + + // Read the artifact which contains `abi`, `bytecode`, `deployedBytecode`,and `metadata` + let artifact = std::fs::read(path).unwrap(); + let json: serde_json::Value = serde_json::from_slice(&artifact)?; + + // Get `abi` from the artifact + let abi_val = json.get("abi").unwrap(); + let abi = serde_json::from_str(&abi_val.to_string())?; + + // Create a new `ContractInstance` of the Counter contract from the abi + let counter_instance: ContractInstance, _, Ethereum> = + ContractInstance::new(contract_address, provider.clone(), Interface::new(abi)); + + // Interact with the contract + assert_eq!(counter_instance.abi().functions().count(), 3); + + // Read + let init_val = counter_instance.function("number", &[])?.call().await?; + + // Get the Uint value from the result + let init_val = init_val.first().unwrap().as_uint().unwrap().0; + + assert_eq!(U256::from(0), init_val); + + // Increment + let incr_receipt = + counter_instance.function("increment", &[])?.send().await?.get_receipt().await?; + + assert!(incr_receipt.status()); + + let incremented_val = counter_instance.function("number", &[])?.call().await?; + + let incremented_val = incremented_val.first().unwrap().as_uint().unwrap().0; + + assert_eq!(U256::from(1), incremented_val); + + // Set the number + let set_val = DynSolValue::from(U256::from(100)); + + let set_receipt = + counter_instance.function("setNumber", &[set_val])?.send().await?.get_receipt().await?; + + assert!(set_receipt.status()); + + let set_val = counter_instance.function("number", &[])?.call().await?; + + let set_val = set_val.first().unwrap().as_uint().unwrap().0; + + assert_eq!(U256::from(100), set_val); + + // Try calling a function that does not exist + let decr_call = counter_instance.function("decrement", &[]).unwrap_err(); + + assert!(decr_call.to_string().contains("function decrement does not exist")); + + Ok(()) +} diff --git a/examples/contracts/examples/dyn_sol_struct_tuples.rs b/examples/contracts/examples/dyn_sol_struct_tuples.rs new file mode 100644 index 00000000..e8d0f52e --- /dev/null +++ b/examples/contracts/examples/dyn_sol_struct_tuples.rs @@ -0,0 +1,96 @@ +//! This example demonstrates how can one handle unknown/complex return types using DynSol. +use alloy::{ + contract::{ContractInstance, Interface}, + dyn_abi::DynSolValue, + json_abi::JsonAbi, + network::{Ethereum, TransactionBuilder}, + primitives::{hex, U256}, + providers::{Provider, ProviderBuilder}, + rpc::types::TransactionRequest, + transports::http::{Client, Http}, +}; +use eyre::Result; + +/** + * contract Colors { + struct Color { + uint8 r; + uint8 g; + uint8 b; + } + + mapping(address => Color) public colors; + + function setColor(uint8 r, uint8 g, uint8 b) public { + colors[msg.sender] = Color(r, g, b); + } + + function getColor(address user) public view returns (Color memory) { + return colors[user]; + } + + function getColorAsTuple( + address user + ) public view returns (uint8, uint8, uint8) { + return (colors[user].r, colors[user].g, colors[user].b); + } +} + */ + +#[tokio::main] +async fn main() -> Result<()> { + let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet(); + + let from = provider.get_accounts().await?[0]; + let bytecode = hex::decode("6080604052348015600f57600080fd5b506105fb8061001f6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063610c76f01461005157806384b5e5961461006d57806399efff171461009d578063befdb4f6146100cf575b600080fd5b61006b60048036038101906100669190610435565b610101565b005b610087600480360381019061008291906104e6565b6101ce565b6040516100949190610564565b60405180910390f35b6100b760048036038101906100b291906104e6565b61027d565b6040516100c69392919061058e565b60405180910390f35b6100e960048036038101906100e491906104e6565b61037c565b6040516100f89392919061058e565b60405180910390f35b60405180606001604052808460ff1681526020018360ff1681526020018260ff168152506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548160ff021916908360ff16021790555060208201518160000160016101000a81548160ff021916908360ff16021790555060408201518160000160026101000a81548160ff021916908360ff160217905550905050505050565b6101d66103cd565b6000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060600160405290816000820160009054906101000a900460ff1660ff1660ff1681526020016000820160019054906101000a900460ff1660ff1660ff1681526020016000820160029054906101000a900460ff1660ff1660ff16815250509050919050565b60008060008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff166000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160019054906101000a900460ff166000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160029054906101000a900460ff169250925092509193909250565b60006020528060005260406000206000915090508060000160009054906101000a900460ff16908060000160019054906101000a900460ff16908060000160029054906101000a900460ff16905083565b6040518060600160405280600060ff168152602001600060ff168152602001600060ff1681525090565b600080fd5b600060ff82169050919050565b610412816103fc565b811461041d57600080fd5b50565b60008135905061042f81610409565b92915050565b60008060006060848603121561044e5761044d6103f7565b5b600061045c86828701610420565b935050602061046d86828701610420565b925050604061047e86828701610420565b9150509250925092565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104b382610488565b9050919050565b6104c3816104a8565b81146104ce57600080fd5b50565b6000813590506104e0816104ba565b92915050565b6000602082840312156104fc576104fb6103f7565b5b600061050a848285016104d1565b91505092915050565b61051c816103fc565b82525050565b6060820160008201516105386000850182610513565b50602082015161054b6020850182610513565b50604082015161055e6040850182610513565b50505050565b60006060820190506105796000830184610522565b92915050565b610588816103fc565b82525050565b60006060820190506105a3600083018661057f565b6105b0602083018561057f565b6105bd604083018461057f565b94935050505056fea2646970667358221220ce426adf2fbf80a861f23a5eb1e99a281bb07e427b9beed059e09c285f16db6c64736f6c634300081a0033")?; + let deploy_tx = TransactionRequest::default().from(from).with_deploy_code(bytecode); + + let contract_address = + provider.send_transaction(deploy_tx).await?.get_receipt().await?.contract_address.unwrap(); + + // Get the contract abi + let path = std::env::current_dir()?.join("examples/contracts/examples/abi/Colors.json"); + let contents = std::fs::read(path)?; + let abi: JsonAbi = serde_json::from_slice(&contents)?; + + // Create a new `ContractInstance` of the Counter contract from the abi + let counter_instance: ContractInstance, _, Ethereum> = + ContractInstance::new(contract_address, provider.clone(), Interface::new(abi)); + + // Interact with the contract + assert_eq!(counter_instance.abi().functions().count(), 4); + + // Set color to White + let r = DynSolValue::Uint(U256::from(255), 8); // uint8 + let g = DynSolValue::Uint(U256::from(255), 8); // uint8 + let b = DynSolValue::Uint(U256::from(255), 8); // uint8 + let set_color_func = counter_instance.function("setColor", &[r, g, b])?; + let set_color_receipt = set_color_func.send().await?.get_receipt().await?; + assert!(set_color_receipt.status()); + + // Get the color + let get_color_func = counter_instance.function("getColor", &[DynSolValue::Address(from)])?; + let get_color_result = get_color_func.call().await?; + + // The `r`, `g`, `b` values in the `Color` struct get converted to a `DynSolValue::Tuple` + assert!(get_color_result.len() == 1); + for value in get_color_result { + if let DynSolValue::Tuple(struct_as_tuple) = value { + println!("{struct_as_tuple:?}"); + } + } + + // Get the color as tuple + let get_color_tuple = + counter_instance.function("getColorAsTuple", &[DynSolValue::Address(from)])?; + let get_color_tuple_result = get_color_tuple.call().await?; + + // The `r`, `g`, `b` are returned as a solidity tuple and hence represented as individual + // `DynSolValue::Uint` + assert!(get_color_tuple_result.len() == 3); + for value in get_color_tuple_result { + println!("{value:?}"); + } + + Ok(()) +}