diff --git a/Cargo.toml b/Cargo.toml index f8699f2..884f6f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ significant_drop_tightening = "allow" needless_return = "allow" [workspace.dependencies] -alloy = { version = "0.9", features = [ +alloy = { git = "https://github.com/alloy-rs/alloy", branch = "yash/core-patch", features = [ "eips", "full", "hyper", @@ -128,5 +128,8 @@ serde = "1.0" serde_json = "1.0" [patch.crates-io] -# alloy = { git = "https://github.com/alloy-rs/alloy", rev = "65dfbe" } -# foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "d113d6e" } +alloy-core = { git = "https://github.com/alloy-rs/core", branch = "yash/fix-fn-ret" } +alloy-sol-types = { git = "https://github.com/alloy-rs/core", branch = "yash/fix-fn-ret" } +alloy-primitives = { git = "https://github.com/alloy-rs/core", branch = "yash/fix-fn-ret" } +alloy-dyn-abi = { git = "https://github.com/alloy-rs/core", branch = "yash/fix-fn-ret" } +# alloy = { git = "https://github.com/alloy-rs/alloy", branch = "yash/core-patch" } diff --git a/README.md b/README.md index 9374e76..02a598c 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ This repository contains the following examples: - [x] [Contracts](./examples/contracts/examples/deploy_from_contract.rs) - [x] [Events and errors](./examples/sol-macro/examples/events_errors.rs) - [x] [Decode returns](./examples/sol-macro/examples/decode_returns.rs) + - [x] [Deconstructing complex return types](./examples/sol-macro/examples/complex_returns.rs) - [x] [Structs and enums](./examples/sol-macro/examples/structs_enums.rs) - [x] [User defined types](./examples/sol-macro/examples/user_defined_types.rs) - [x] Transactions diff --git a/examples/contracts/examples/deploy_from_artifact.rs b/examples/contracts/examples/deploy_from_artifact.rs index 60e93e9..60505b0 100644 --- a/examples/contracts/examples/deploy_from_artifact.rs +++ b/examples/contracts/examples/deploy_from_artifact.rs @@ -43,7 +43,7 @@ async fn main() -> Result<()> { // return value must be accessed by index - as if it is an unnamed value. // If you prefer to use named return values, it is recommended to embed the Solidity code // directly in the `sol!` macro as shown in `deploy_from_contract.rs`. - let number = builder.call().await?._0; + let number = builder.call().await?; println!("Retrieved number: {number}"); diff --git a/examples/contracts/examples/deploy_from_bytecode.rs b/examples/contracts/examples/deploy_from_bytecode.rs index 8cecfa2..d3dc4bc 100644 --- a/examples/contracts/examples/deploy_from_bytecode.rs +++ b/examples/contracts/examples/deploy_from_bytecode.rs @@ -64,7 +64,7 @@ async fn main() -> Result<()> { // Retrieve the number, which should be 43. let builder = contract.number(); - let number = builder.call().await?.number.to_string(); + let number = builder.call().await?.to_string(); println!("Retrieved number: {number}"); diff --git a/examples/contracts/examples/deploy_from_contract.rs b/examples/contracts/examples/deploy_from_contract.rs index ca47f67..1a191c6 100644 --- a/examples/contracts/examples/deploy_from_contract.rs +++ b/examples/contracts/examples/deploy_from_contract.rs @@ -46,7 +46,7 @@ async fn main() -> Result<()> { // Retrieve the number, which should be 43. let builder = contract.number(); - let number = builder.call().await?.number.to_string(); + let number = builder.call().await?.to_string(); println!("Retrieved number: {number}"); diff --git a/examples/contracts/examples/interact_with_abi.rs b/examples/contracts/examples/interact_with_abi.rs index 6b80623..16e4121 100644 --- a/examples/contracts/examples/interact_with_abi.rs +++ b/examples/contracts/examples/interact_with_abi.rs @@ -23,7 +23,7 @@ async fn main() -> Result<()> { let contract = IWETH9::new(address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), provider); // Call the contract, retrieve the total supply. - let total_supply = contract.totalSupply().call().await?._0; + let total_supply = contract.totalSupply().call().await?; println!("WETH total supply is {total_supply}"); diff --git a/examples/node-bindings/examples/anvil_deploy_contract.rs b/examples/node-bindings/examples/anvil_deploy_contract.rs index eaa48d1..48a80f1 100644 --- a/examples/node-bindings/examples/anvil_deploy_contract.rs +++ b/examples/node-bindings/examples/anvil_deploy_contract.rs @@ -46,7 +46,7 @@ async fn main() -> Result<()> { // Retrieve the number, which should be 43. let builder = contract.number(); - let number = builder.call().await?.number.to_string(); + let number = builder.call().await?.to_string(); println!("Retrieved number: {number}"); diff --git a/examples/node-bindings/examples/anvil_set_storage_at.rs b/examples/node-bindings/examples/anvil_set_storage_at.rs index eea1f62..1a91363 100644 --- a/examples/node-bindings/examples/anvil_set_storage_at.rs +++ b/examples/node-bindings/examples/anvil_set_storage_at.rs @@ -32,7 +32,7 @@ async fn main() -> Result<()> { let account = address!("F605F9d1cB055E87E30bcAEe4CB9389a35aBe8Ff"); // Get the WETH balance of the target account before mocking. - let balance_before = iweth.balanceOf(account).call().await?._0; + let balance_before = iweth.balanceOf(account).call().await?; println!("WETH balance before: {}", balance_before); assert_eq!(balance_before, U256::ZERO); @@ -42,7 +42,7 @@ async fn main() -> Result<()> { provider.anvil_set_storage_at(WETH_ADDR, hashed_slot.into(), mocked_balance.into()).await?; // Get the WETH balance of the target account after mocking. - let balance_after = iweth.balanceOf(account).call().await?._0; + let balance_after = iweth.balanceOf(account).call().await?; println!("WETH balance after: {}", balance_after); assert_eq!(balance_after, mocked_balance); diff --git a/examples/sol-macro/examples/complex_returns.rs b/examples/sol-macro/examples/complex_returns.rs new file mode 100644 index 0000000..9e90dce --- /dev/null +++ b/examples/sol-macro/examples/complex_returns.rs @@ -0,0 +1,111 @@ +//! Example showing how to deconstruct complex return values such as tuple, structs, etc. are +//! returned from a call to a contract using the `sol!` macro. + +use alloy::{ + hex, + primitives::{Uint, I256, U256}, + sol, + sol_types::{SolCall, SolValue}, +}; +use eyre::Result; + +sol! { + function getNamedTuple() external view returns (uint256 a, uint256 b, uint256 c); + function getUnamedTuple() external view returns (uint256, uint256, uint256); + function getPartialNamedTuple() external view returns (uint256 , uint256 b, uint256 ); + + struct MyStruct { + uint256 a; + uint256 b; + uint256 c; + } + + function getStructWithBytes() external view returns (MyStruct my_struct, bytes32); + function getCompoundTupleStruct() external view returns ((MyStruct , bytes32), (MyStruct, bytes32)); +} + +fn main() -> Result<()> { + let data = vec![1, 2, 3].abi_encode_sequence(); + + // Return param names are retained as field names in the struct. + let getNamedTupleReturn { a, b, c } = getNamedTupleCall::abi_decode_returns(&data, true)?; + + assert_eq!(a, U256::from(1)); + assert_eq!(b, U256::from(2)); + assert_eq!(c, U256::from(3)); + + // Struct fields are named `_{index}` in case a return param is left unnamed. + let getUnamedTupleReturn { _0: a, _1: b, _2: c } = + getUnamedTupleCall::abi_decode_returns(&data, true)?; + + assert_eq!(a, U256::from(1)); + assert_eq!(b, U256::from(2)); + assert_eq!(c, U256::from(3)); + + // Indicates a case where only one of the return param is named and the rest are unnamed. + let getPartialNamedTupleReturn { _0: a, b, _2: c } = + getPartialNamedTupleCall::abi_decode_returns(&data, true)?; + + assert_eq!(a, U256::from(1)); + assert_eq!(b, U256::from(2)); + assert_eq!(c, U256::from(3)); + + let data = hex!( + // MyStruct.a (uint256) + "0000000000000000000000000000000000000000000000000000000000000001" + // MyStruct.b (uint256) + "0000000000000000000000000000000000000000000000000000000000000002" + // MyStruct.c (uint256) + "0000000000000000000000000000000000000000000000000000000000000003" + // bytes32 + "0102030400000000000000000000000000000000000000000000000000000000" + ); + + // Deconstruct a struct and bytes32 return value. + let getStructWithBytesReturn { my_struct: MyStruct { a, b, c }, _1 } = + getStructWithBytesCall::abi_decode_returns(&data, true)?; + + assert_eq!(a, U256::from(1)); + assert_eq!(b, U256::from(2)); + assert_eq!(c, U256::from(3)); + assert_eq!(bytes, b256!("0102030400000000000000000000000000000000000000000000000000000000")); + + let data = hex!( + // First tuple: (MyStruct, bytes32) + // MyStruct.a (uint256) + "0000000000000000000000000000000000000000000000000000000000000001" + // MyStruct.b (uint256) + "0000000000000000000000000000000000000000000000000000000000000002" + // MyStruct.c (uint256) + "0000000000000000000000000000000000000000000000000000000000000003" + // First bytes32 + "0102030400000000000000000000000000000000000000000000000000000000" + // Second tuple: (MyStruct, bytes32) + // MyStruct.a (uint256) + "0000000000000000000000000000000000000000000000000000000000000004" + // MyStruct.b (uint256) + "0000000000000000000000000000000000000000000000000000000000000005" + // MyStruct.c (uint256) + "0000000000000000000000000000000000000000000000000000000000000006" + // Second bytes32 + "0506070800000000000000000000000000000000000000000000000000000000" + ); + + let getCompoundTupleStructReturn { _0, _1 } = + getCompoundTupleStructCall::abi_decode_returns(&data, true)?; + + let (MyStruct { a, b, c }, bytes) = _0; + let (MyStruct { a: a2, b: b2, c: c2 }, bytes2) = _1; + + assert_eq!(a, U256::from(1)); + assert_eq!(b, U256::from(2)); + assert_eq!(c, U256::from(3)); + assert_eq!(bytes, b256!("0102030400000000000000000000000000000000000000000000000000000000")); + + assert_eq!(a2, U256::from(4)); + assert_eq!(b2, U256::from(5)); + assert_eq!(c2, U256::from(6)); + assert_eq!(bytes2, b256!("0506070800000000000000000000000000000000000000000000000000000000")); + + Ok(()) +} diff --git a/examples/sol-macro/examples/decode_returns.rs b/examples/sol-macro/examples/decode_returns.rs index 9813028..bbd3a14 100644 --- a/examples/sol-macro/examples/decode_returns.rs +++ b/examples/sol-macro/examples/decode_returns.rs @@ -17,27 +17,23 @@ sol!( ); fn main() -> Result<()> { - let result = getRoundDataCall::abi_decode_returns( - &hex!( - "0000000000000000000000000000000000000000000000060000000000004716 + let getRoundDataReturn { roundId, answer, startedAt, updatedAt, answeredInRound } = + getRoundDataCall::abi_decode_returns( + &hex!( + "0000000000000000000000000000000000000000000000060000000000004716 00000000000000000000000000000000000000000000000000000051faad1c80 000000000000000000000000000000000000000000000000000000006669627b 000000000000000000000000000000000000000000000000000000006669627b 0000000000000000000000000000000000000000000000060000000000004716" - ), - true, - ); + ), + true, + )?; - assert_eq!( - result, - Ok(getRoundDataReturn { - roundId: Uint::<80, 2>::from(110680464442257327894_u128), - answer: I256::from_dec_str("352098000000")?, - startedAt: U256::from(1718182523), - updatedAt: U256::from(1718182523), - answeredInRound: Uint::<80, 2>::from(110680464442257327894_u128), - }) - ); + assert_eq!(roundId, Uint::<80, 2>::from(110680464442257327894_u128)); + assert_eq!(answer, I256::from_dec_str("352098000000")?); + assert_eq!(startedAt, U256::from(1718182523)); + assert_eq!(updatedAt, U256::from(1718182523)); + assert_eq!(answeredInRound, Uint::<80, 2>::from(110680464442257327894_u128)); Ok(()) } diff --git a/examples/transactions/examples/transfer_erc20.rs b/examples/transactions/examples/transfer_erc20.rs index e70a78c..bee47aa 100644 --- a/examples/transactions/examples/transfer_erc20.rs +++ b/examples/transactions/examples/transfer_erc20.rs @@ -33,8 +33,8 @@ async fn main() -> Result<()> { let contract = ERC20Example::deploy(provider).await?; // Register the balances of Alice and Bob before the transfer. - let alice_before_balance = contract.balanceOf(alice).call().await?._0; - let bob_before_balance = contract.balanceOf(bob).call().await?._0; + let alice_before_balance = contract.balanceOf(alice).call().await?; + let bob_before_balance = contract.balanceOf(bob).call().await?; // Transfer and wait for inclusion. let amount = U256::from(100); @@ -43,8 +43,8 @@ async fn main() -> Result<()> { println!("Sent transaction: {tx_hash}"); // Register the balances of Alice and Bob after the transfer. - let alice_after_balance = contract.balanceOf(alice).call().await?._0; - let bob_after_balance = contract.balanceOf(bob).call().await?._0; + let alice_after_balance = contract.balanceOf(alice).call().await?; + let bob_after_balance = contract.balanceOf(bob).call().await?; // Check the balances of Alice and Bob after the transfer. assert_eq!(alice_before_balance - alice_after_balance, amount); diff --git a/examples/transactions/examples/with_access_list.rs b/examples/transactions/examples/with_access_list.rs index 936a57f..9a7b4e3 100644 --- a/examples/transactions/examples/with_access_list.rs +++ b/examples/transactions/examples/with_access_list.rs @@ -51,7 +51,7 @@ async fn main() -> Result<()> { println!("Transaction hash: {tx_hash}"); // Check the value of the contract. - let value = contract.getValue().call().await?._0; + let value = contract.getValue().call().await?; assert_eq!(value, "hello");