2023-03-01 00:26:27 +00:00
|
|
|
use crate::common::*;
|
|
|
|
use ethers_contract::{
|
2023-03-18 18:46:17 +00:00
|
|
|
abigen, ContractFactory, ContractInstance, Eip712, EthAbiType, EthEvent, LogMeta, Multicall,
|
2023-03-01 00:26:27 +00:00
|
|
|
MulticallError, MulticallVersion,
|
|
|
|
};
|
|
|
|
use ethers_core::{
|
|
|
|
abi::{encode, AbiEncode, Token, Tokenizable},
|
2023-03-21 18:44:12 +00:00
|
|
|
types::{Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, U256},
|
2023-03-01 00:26:27 +00:00
|
|
|
utils::{keccak256, Anvil},
|
|
|
|
};
|
2023-03-18 18:46:17 +00:00
|
|
|
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt, Ws};
|
2023-03-01 00:26:27 +00:00
|
|
|
use ethers_signers::{LocalWallet, Signer};
|
2023-03-16 19:28:35 +00:00
|
|
|
use std::{sync::Arc, time::Duration};
|
2023-03-01 00:26:27 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct NonClone<M> {
|
|
|
|
m: M,
|
|
|
|
}
|
2023-02-21 00:27:43 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MwErr<M: Middleware>(M::Error);
|
2020-06-01 23:15:33 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
impl<M> MiddlewareError for MwErr<M>
|
|
|
|
where
|
|
|
|
M: Middleware,
|
|
|
|
{
|
|
|
|
type Inner = M::Error;
|
2020-06-17 09:22:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
fn from_err(src: M::Error) -> Self {
|
|
|
|
Self(src)
|
2023-02-06 21:27:01 +00:00
|
|
|
}
|
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
fn as_inner(&self) -> Option<&Self::Inner> {
|
|
|
|
Some(&self.0)
|
2023-02-06 21:27:01 +00:00
|
|
|
}
|
2023-03-01 00:26:27 +00:00
|
|
|
}
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
impl<M: Middleware> std::fmt::Display for MwErr<M> {
|
|
|
|
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
Ok(())
|
2023-02-06 21:27:01 +00:00
|
|
|
}
|
2023-03-01 00:26:27 +00:00
|
|
|
}
|
|
|
|
impl<M: Middleware> std::error::Error for MwErr<M> {}
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
impl<M: Middleware> Middleware for NonClone<M> {
|
|
|
|
type Error = MwErr<M>;
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
type Provider = M::Provider;
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
type Inner = M;
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
fn inner(&self) -> &Self::Inner {
|
|
|
|
&self.m
|
2023-02-06 21:27:01 +00:00
|
|
|
}
|
2023-03-01 00:26:27 +00:00
|
|
|
}
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// this is not a test. It is a compile check. :)
|
|
|
|
// It exists to ensure that trait bounds on contract internal behave as
|
|
|
|
// expected. It should not be run
|
|
|
|
fn _it_compiles() {
|
|
|
|
let (abi, _bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// launch anvil
|
|
|
|
let anvil = Anvil::new().spawn();
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let client = Provider::<Http>::try_from(anvil.endpoint())
|
|
|
|
.unwrap()
|
|
|
|
.interval(Duration::from_millis(10u64));
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// Works (B == M, M: Clone)
|
|
|
|
let c: ContractInstance<&Provider<Http>, Provider<Http>> =
|
|
|
|
ContractInstance::new(H160::default(), abi.clone(), &client);
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let _ = c.method::<(), ()>("notARealMethod", ());
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// Works (B == &M, M: Clone)
|
|
|
|
let c: ContractInstance<Provider<Http>, Provider<Http>> =
|
|
|
|
ContractInstance::new(H160::default(), abi.clone(), client.clone());
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let _ = c.method::<(), ()>("notARealMethod", ());
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let non_clone_mware = NonClone { m: client };
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// Works (B == &M, M: !Clone)
|
|
|
|
let c: ContractInstance<&NonClone<Provider<Http>>, NonClone<Provider<Http>>> =
|
|
|
|
ContractInstance::new(H160::default(), abi, &non_clone_mware);
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let _ = c.method::<(), ()>("notARealMethod", ());
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// // Fails (B == M, M: !Clone)
|
|
|
|
// let c: ContractInternal<NonClone<Provider<Http>>, NonClone<Provider<Http>>> =
|
|
|
|
// ContractInternal::new(H160::default(), abi, non_clone_mware);
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// let _ = c.method::<(), ()>("notARealMethod", ());
|
|
|
|
}
|
2023-02-06 21:27:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn deploy_and_call_contract() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
|
|
|
|
// launch anvil
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
|
|
|
|
// Instantiate the clients. We assume that clients consume the provider and the wallet
|
|
|
|
// (which makes sense), so for multi-client tests, you must clone the provider.
|
|
|
|
let addrs = anvil.addresses().to_vec();
|
|
|
|
let addr2 = addrs[1];
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let client2 = connect(&anvil, 1);
|
|
|
|
|
|
|
|
// create a factory which will be used to deploy instances of the contract
|
|
|
|
let factory = ContractFactory::new(abi, bytecode, client.clone());
|
|
|
|
|
|
|
|
// `send` consumes the deployer so it must be cloned for later re-use
|
|
|
|
// (practically it's not expected that you'll need to deploy multiple instances of
|
|
|
|
// the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff)
|
|
|
|
let deployer = factory.deploy("initial value".to_string()).unwrap().legacy();
|
|
|
|
// dry runs the deployment of the contract. takes the deployer by reference, no need to
|
|
|
|
// clone.
|
|
|
|
deployer.call().await.unwrap();
|
|
|
|
let (contract, receipt) = deployer.clone().send_with_receipt().await.unwrap();
|
|
|
|
assert_eq!(receipt.contract_address.unwrap(), contract.address());
|
|
|
|
|
|
|
|
let get_value = contract.method::<_, String>("getValue", ()).unwrap();
|
|
|
|
let last_sender = contract.method::<_, Address>("lastSender", ()).unwrap();
|
|
|
|
|
|
|
|
// the initial value must be the one set in the constructor
|
|
|
|
let value = get_value.clone().call().await.unwrap();
|
|
|
|
assert_eq!(value, "initial value");
|
|
|
|
|
|
|
|
// need to declare the method first, and only then send it
|
|
|
|
// this is because it internally clones an Arc which would otherwise
|
|
|
|
// get immediately dropped
|
|
|
|
let contract_call =
|
|
|
|
contract.connect(client2.clone()).method::<_, H256>("setValue", "hi".to_owned()).unwrap();
|
|
|
|
let calldata = contract_call.calldata().unwrap();
|
|
|
|
let _gas_estimate = contract_call.estimate_gas().await.unwrap();
|
|
|
|
let contract_call = contract_call.legacy();
|
|
|
|
let pending_tx = contract_call.send().await.unwrap();
|
|
|
|
let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap();
|
|
|
|
let _tx_receipt = pending_tx.await.unwrap().unwrap();
|
|
|
|
assert_eq!(last_sender.clone().call().await.unwrap(), addr2);
|
|
|
|
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
|
|
|
assert_eq!(tx.input, calldata);
|
|
|
|
|
|
|
|
// we can also call contract methods at other addresses with the `at` call
|
|
|
|
// (useful when interacting with multiple ERC20s for example)
|
|
|
|
let contract2_addr = deployer.send().await.unwrap().address();
|
|
|
|
let contract2 = contract.at(contract2_addr);
|
|
|
|
let init_value: String =
|
|
|
|
contract2.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
|
|
|
let init_address =
|
|
|
|
contract2.method::<_, Address>("lastSender", ()).unwrap().call().await.unwrap();
|
|
|
|
assert_eq!(init_address, Address::zero());
|
|
|
|
assert_eq!(init_value, "initial value");
|
|
|
|
|
|
|
|
// methods with multiple args also work
|
|
|
|
let _tx_hash = contract
|
|
|
|
.method::<_, H256>("setValues", ("hi".to_owned(), "bye".to_owned()))
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
2020-06-17 09:22:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[cfg(feature = "abigen")]
|
|
|
|
async fn get_past_events() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let address = client.get_accounts().await.unwrap()[0];
|
|
|
|
let contract = deploy(client.clone(), abi, bytecode).await;
|
|
|
|
|
|
|
|
// make a call with `client`
|
|
|
|
let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap().legacy();
|
|
|
|
let tx = func.send().await.unwrap();
|
|
|
|
let _receipt = tx.await.unwrap();
|
|
|
|
|
|
|
|
// and we can fetch the events
|
|
|
|
let logs: Vec<ValueChanged> = contract
|
|
|
|
.event()
|
|
|
|
.from_block(0u64)
|
|
|
|
.topic1(address) // Corresponds to the first indexed parameter
|
|
|
|
.query()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(logs[0].new_value, "initial value");
|
|
|
|
assert_eq!(logs[1].new_value, "hi");
|
|
|
|
assert_eq!(logs.len(), 2);
|
|
|
|
|
|
|
|
// and we can fetch the events at a block hash
|
|
|
|
let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap();
|
|
|
|
let logs: Vec<ValueChanged> = contract
|
|
|
|
.event()
|
|
|
|
.at_block_hash(hash)
|
|
|
|
.topic1(address) // Corresponds to the first indexed parameter
|
|
|
|
.query()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(logs[0].new_value, "initial value");
|
|
|
|
assert_eq!(logs.len(), 1);
|
|
|
|
}
|
2021-03-16 19:46:07 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[cfg(feature = "abigen")]
|
|
|
|
async fn get_events_with_meta() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let address = anvil.addresses()[0];
|
|
|
|
let contract = deploy(client.clone(), abi, bytecode).await;
|
|
|
|
|
|
|
|
// and we can fetch the events
|
|
|
|
let logs: Vec<(ValueChanged, LogMeta)> = contract
|
|
|
|
.event()
|
|
|
|
.from_block(0u64)
|
|
|
|
.topic1(address) // Corresponds to the first indexed parameter
|
|
|
|
.query_with_meta()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(logs.len(), 1);
|
|
|
|
let (log, meta) = &logs[0];
|
|
|
|
assert_eq!(log.new_value, "initial value");
|
|
|
|
|
|
|
|
assert_eq!(meta.address, contract.address());
|
|
|
|
assert_eq!(meta.log_index, 0.into());
|
|
|
|
assert_eq!(meta.block_number, 1.into());
|
|
|
|
let block = client.get_block(1).await.unwrap().unwrap();
|
|
|
|
assert_eq!(meta.block_hash, block.hash.unwrap());
|
|
|
|
assert_eq!(block.transactions.len(), 1);
|
|
|
|
let tx = block.transactions[0];
|
|
|
|
assert_eq!(meta.transaction_hash, tx);
|
|
|
|
assert_eq!(meta.transaction_index, 0.into());
|
|
|
|
}
|
2021-05-13 10:51:27 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn call_past_state() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let contract = deploy(client.clone(), abi, bytecode).await;
|
|
|
|
let deployed_block = client.get_block_number().await.unwrap();
|
|
|
|
|
|
|
|
// assert initial state
|
|
|
|
let value =
|
|
|
|
contract.method::<_, String>("getValue", ()).unwrap().legacy().call().await.unwrap();
|
|
|
|
assert_eq!(value, "initial value");
|
|
|
|
|
|
|
|
// make a call with `client`
|
|
|
|
let _tx_hash = *contract
|
|
|
|
.method::<_, H256>("setValue", "hi".to_owned())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// assert new value
|
|
|
|
let value =
|
|
|
|
contract.method::<_, String>("getValue", ()).unwrap().legacy().call().await.unwrap();
|
|
|
|
assert_eq!(value, "hi");
|
|
|
|
|
|
|
|
// assert previous value
|
|
|
|
let value = contract
|
|
|
|
.method::<_, String>("getValue", ())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.block(BlockId::Number(deployed_block.into()))
|
|
|
|
.call()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(value, "initial value");
|
|
|
|
|
|
|
|
// Here would be the place to test EIP-1898, specifying the `BlockId` of `call` as the
|
|
|
|
// first block hash. However, Ganache does not implement this :/
|
|
|
|
|
|
|
|
// let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap();
|
|
|
|
// let value = contract
|
|
|
|
// .method::<_, String>("getValue", ())
|
|
|
|
// .unwrap()
|
|
|
|
// .block(BlockId::Hash(hash))
|
|
|
|
// .call()
|
|
|
|
// .await
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(value, "initial value");
|
|
|
|
}
|
2021-03-16 19:46:07 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[ignore]
|
|
|
|
async fn call_past_hash_test() {
|
|
|
|
// geth --dev --http --http.api eth,web3
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
|
|
|
let deployer = provider.get_accounts().await.unwrap()[0];
|
|
|
|
|
|
|
|
let client = Arc::new(provider.with_sender(deployer));
|
|
|
|
let contract = deploy(client.clone(), abi, bytecode).await;
|
|
|
|
let deployed_block = client.get_block_number().await.unwrap();
|
|
|
|
|
|
|
|
// assert initial state
|
|
|
|
let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
|
|
|
assert_eq!(value, "initial value");
|
|
|
|
|
|
|
|
// make a call with `client`
|
|
|
|
let _tx_hash =
|
|
|
|
*contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap().send().await.unwrap();
|
|
|
|
|
|
|
|
// assert new value
|
|
|
|
let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
|
|
|
assert_eq!(value, "hi");
|
|
|
|
|
|
|
|
// assert previous value using block hash
|
|
|
|
let hash = client.get_block(deployed_block).await.unwrap().unwrap().hash.unwrap();
|
|
|
|
let value = contract
|
|
|
|
.method::<_, String>("getValue", ())
|
|
|
|
.unwrap()
|
|
|
|
.block(BlockId::Hash(hash))
|
|
|
|
.call()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(value, "initial value");
|
|
|
|
}
|
2020-06-17 09:22:01 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn watch_events() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let contract = deploy(client.clone(), abi.clone(), bytecode).await;
|
2020-06-21 18:19:59 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
// We spawn the event listener:
|
|
|
|
let event = contract.event::<ValueChanged>();
|
|
|
|
let mut stream = event.stream().await.unwrap();
|
|
|
|
|
|
|
|
// Also set up a subscription for the same thing
|
2023-03-18 18:46:17 +00:00
|
|
|
let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
|
2023-03-01 00:26:27 +00:00
|
|
|
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
|
|
|
|
let event2 = contract2.event::<ValueChanged>();
|
|
|
|
let mut subscription = event2.subscribe().await.unwrap();
|
2021-08-12 16:19:24 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let mut subscription_meta = event2.subscribe().await.unwrap().with_meta();
|
|
|
|
|
|
|
|
let num_calls = 3u64;
|
|
|
|
|
|
|
|
// and we make a few calls
|
|
|
|
let num = client.get_block_number().await.unwrap();
|
|
|
|
for i in 0..num_calls {
|
|
|
|
let call = contract.method::<_, H256>("setValue", i.to_string()).unwrap().legacy();
|
2021-08-12 16:19:24 +00:00
|
|
|
let pending_tx = call.send().await.unwrap();
|
|
|
|
let _receipt = pending_tx.await.unwrap();
|
2023-03-01 00:26:27 +00:00
|
|
|
}
|
2021-08-12 16:19:24 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
for i in 0..num_calls {
|
2021-08-12 16:19:24 +00:00
|
|
|
// unwrap the option of the stream, then unwrap the decoding result
|
2023-03-01 00:26:27 +00:00
|
|
|
let log = stream.next().await.unwrap().unwrap();
|
|
|
|
let log2 = subscription.next().await.unwrap().unwrap();
|
|
|
|
let (log3, meta) = subscription_meta.next().await.unwrap().unwrap();
|
|
|
|
assert_eq!(log.new_value, log3.new_value);
|
|
|
|
assert_eq!(log.new_value, log2.new_value);
|
|
|
|
assert_eq!(log.new_value, i.to_string());
|
|
|
|
assert_eq!(meta.block_number, num + i + 1);
|
|
|
|
let hash = client.get_block(num + i + 1).await.unwrap().unwrap().hash.unwrap();
|
|
|
|
assert_eq!(meta.block_hash, hash);
|
2021-08-12 16:19:24 +00:00
|
|
|
}
|
2023-03-01 00:26:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn watch_subscription_events_multiple_addresses() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let contract_1 = deploy(client.clone(), abi.clone(), bytecode.clone()).await;
|
|
|
|
let contract_2 = deploy(client.clone(), abi.clone(), bytecode).await;
|
|
|
|
|
2023-03-18 18:46:17 +00:00
|
|
|
let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
|
2023-03-01 00:26:27 +00:00
|
|
|
let filter = Filter::new()
|
|
|
|
.address(ValueOrArray::Array(vec![contract_1.address(), contract_2.address()]));
|
|
|
|
let mut stream = ws.subscribe_logs(&filter).await.unwrap();
|
|
|
|
|
|
|
|
// and we make a few calls
|
|
|
|
let call = contract_1.method::<_, H256>("setValue", "1".to_string()).unwrap().legacy();
|
|
|
|
let pending_tx = call.send().await.unwrap();
|
|
|
|
let _receipt = pending_tx.await.unwrap();
|
|
|
|
|
|
|
|
let call = contract_2.method::<_, H256>("setValue", "2".to_string()).unwrap().legacy();
|
|
|
|
let pending_tx = call.send().await.unwrap();
|
|
|
|
let _receipt = pending_tx.await.unwrap();
|
|
|
|
|
|
|
|
// unwrap the option of the stream, then unwrap the decoding result
|
|
|
|
let log_1 = stream.next().await.unwrap();
|
|
|
|
let log_2 = stream.next().await.unwrap();
|
|
|
|
assert_eq!(log_1.address, contract_1.address());
|
|
|
|
assert_eq!(log_2.address, contract_2.address());
|
|
|
|
}
|
2021-08-12 16:19:24 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn build_event_of_type() {
|
|
|
|
abigen!(
|
|
|
|
AggregatorInterface,
|
|
|
|
r#"[
|
2022-11-22 21:15:36 +00:00
|
|
|
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt)
|
|
|
|
]"#,
|
2023-03-01 00:26:27 +00:00
|
|
|
);
|
2022-11-22 21:15:36 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let event = ethers_contract::Contract::event_of_type::<AnswerUpdatedFilter>(client);
|
|
|
|
assert_eq!(event.filter, Filter::new().event(&AnswerUpdatedFilter::abi_signature()));
|
|
|
|
}
|
2022-11-22 21:15:36 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn signer_on_node() {
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
// spawn anvil
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
|
|
|
|
// connect
|
|
|
|
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
|
|
|
.unwrap()
|
|
|
|
.interval(std::time::Duration::from_millis(50u64));
|
|
|
|
|
|
|
|
// get the first account
|
|
|
|
let deployer = provider.get_accounts().await.unwrap()[0];
|
|
|
|
let client = Arc::new(provider.with_sender(deployer));
|
|
|
|
|
|
|
|
let contract = deploy(client, abi, bytecode).await;
|
|
|
|
|
|
|
|
// make a call without the signer
|
|
|
|
let _receipt = contract
|
|
|
|
.method::<_, H256>("setValue", "hi".to_owned())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let value: String = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
|
|
|
assert_eq!(value, "hi");
|
|
|
|
}
|
2020-07-03 16:52:09 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn multicall_aggregate() {
|
|
|
|
// get ABI and bytecode for the Multicall contract
|
|
|
|
let (multicall_abi, multicall_bytecode) = compile_contract("Multicall3", "Multicall.sol");
|
|
|
|
|
|
|
|
// get ABI and bytecode for the NotSoSimpleStorage contract
|
|
|
|
let (not_so_simple_abi, not_so_simple_bytecode) =
|
|
|
|
compile_contract("NotSoSimpleStorage", "NotSoSimpleStorage.sol");
|
|
|
|
|
|
|
|
// get ABI and bytecode for the SimpleStorage contract
|
|
|
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
|
|
|
|
|
|
|
// launch anvil
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
|
|
|
|
// Instantiate the clients. We assume that clients consume the provider and the wallet
|
|
|
|
// (which makes sense), so for multi-client tests, you must clone the provider.
|
|
|
|
// `client` is used to deploy the Multicall contract
|
|
|
|
// `client2` is used to deploy the first SimpleStorage contract
|
|
|
|
// `client3` is used to deploy the second SimpleStorage contract
|
|
|
|
// `client4` is used to make the aggregate call
|
|
|
|
let addrs = anvil.addresses().to_vec();
|
|
|
|
let addr2 = addrs[1];
|
|
|
|
let addr3 = addrs[2];
|
|
|
|
let client = connect(&anvil, 0);
|
|
|
|
let client2 = connect(&anvil, 1);
|
|
|
|
let client3 = connect(&anvil, 2);
|
|
|
|
let client4 = connect(&anvil, 3);
|
|
|
|
|
|
|
|
// create a factory which will be used to deploy instances of the contract
|
|
|
|
let multicall_factory = ContractFactory::new(multicall_abi, multicall_bytecode, client.clone());
|
|
|
|
let simple_factory = ContractFactory::new(abi.clone(), bytecode.clone(), client2.clone());
|
|
|
|
let not_so_simple_factory =
|
|
|
|
ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
|
|
|
|
|
|
|
|
let multicall_contract = multicall_factory.deploy(()).unwrap().legacy().send().await.unwrap();
|
|
|
|
let addr = multicall_contract.address();
|
|
|
|
|
|
|
|
let simple_contract =
|
|
|
|
simple_factory.deploy("the first one".to_string()).unwrap().legacy().send().await.unwrap();
|
|
|
|
let not_so_simple_contract = not_so_simple_factory
|
|
|
|
.deploy("the second one".to_string())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// Client2 and Client3 broadcast txs to set the values for both contracts
|
|
|
|
simple_contract
|
|
|
|
.connect(client2.clone())
|
|
|
|
.method::<_, H256>("setValue", "reset first".to_owned())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
not_so_simple_contract
|
|
|
|
.connect(client3.clone())
|
|
|
|
.method::<_, H256>("setValue", "reset second".to_owned())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// get the calls for `value` and `last_sender` for both SimpleStorage contracts
|
|
|
|
let value = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
|
|
|
let value2 = not_so_simple_contract.method::<_, (String, Address)>("getValues", ()).unwrap();
|
|
|
|
let last_sender = simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
|
|
|
let last_sender2 = not_so_simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
|
|
|
|
|
|
|
// initiate the Multicall instance and add calls one by one in builder style
|
|
|
|
let mut multicall = Multicall::new(client4.clone(), Some(addr)).await.unwrap();
|
|
|
|
|
|
|
|
// Set version to 1
|
|
|
|
multicall = multicall.version(MulticallVersion::Multicall);
|
|
|
|
|
|
|
|
multicall
|
|
|
|
.add_call(value, false)
|
|
|
|
.add_call(value2, false)
|
|
|
|
.add_call(last_sender, false)
|
|
|
|
.add_call(last_sender2, false);
|
|
|
|
|
|
|
|
let return_data: (String, (String, Address), Address, Address) =
|
|
|
|
multicall.call().await.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(return_data.0, "reset first");
|
|
|
|
assert_eq!((return_data.1).0, "reset second");
|
|
|
|
assert_eq!((return_data.1).1, addr3);
|
|
|
|
assert_eq!(return_data.2, addr2);
|
|
|
|
assert_eq!(return_data.3, addr3);
|
|
|
|
|
|
|
|
// construct broadcast transactions that will be batched and broadcast via Multicall
|
|
|
|
let broadcast = simple_contract
|
|
|
|
.connect(client4.clone())
|
|
|
|
.method::<_, H256>("setValue", "first reset again".to_owned())
|
|
|
|
.unwrap();
|
|
|
|
let broadcast2 = not_so_simple_contract
|
|
|
|
.connect(client4.clone())
|
|
|
|
.method::<_, H256>("setValue", "second reset again".to_owned())
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// use the already initialised Multicall instance, clearing the previous calls and adding
|
|
|
|
// new calls. Previously we used the `.call()` functionality to do a batch of calls in one
|
|
|
|
// go. Now we will use the `.send()` functionality to broadcast a batch of transactions
|
|
|
|
// in one go
|
|
|
|
let mut multicall_send = multicall.clone();
|
|
|
|
multicall_send.clear_calls().add_call(broadcast, false).add_call(broadcast2, false);
|
|
|
|
|
|
|
|
// broadcast the transaction and wait for it to be mined
|
|
|
|
let _tx_receipt = multicall_send.legacy().send().await.unwrap().await.unwrap();
|
|
|
|
|
|
|
|
// Do another multicall to check the updated return values
|
|
|
|
// The `getValue` calls should return the last value we set in the batched broadcast
|
|
|
|
// The `lastSender` calls should return the address of the Multicall contract, as it is
|
|
|
|
// the one acting as proxy and calling our SimpleStorage contracts (msg.sender)
|
|
|
|
let return_data: (String, (String, Address), Address, Address) =
|
|
|
|
multicall.call().await.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(return_data.0, "first reset again");
|
|
|
|
assert_eq!((return_data.1).0, "second reset again");
|
|
|
|
assert_eq!((return_data.1).1, multicall_contract.address());
|
|
|
|
assert_eq!(return_data.2, multicall_contract.address());
|
|
|
|
assert_eq!(return_data.3, multicall_contract.address());
|
|
|
|
|
|
|
|
let addrs = anvil.addresses();
|
|
|
|
// query ETH balances of multiple addresses
|
|
|
|
// these keys haven't been used to do any tx
|
|
|
|
// so should have 100 ETH
|
|
|
|
multicall
|
|
|
|
.clear_calls()
|
|
|
|
.add_get_eth_balance(addrs[4], false)
|
|
|
|
.add_get_eth_balance(addrs[5], false)
|
|
|
|
.add_get_eth_balance(addrs[6], false);
|
|
|
|
|
|
|
|
let valid_balances = [
|
|
|
|
U256::from(10_000_000_000_000_000_000_000u128),
|
|
|
|
U256::from(10_000_000_000_000_000_000_000u128),
|
|
|
|
U256::from(10_000_000_000_000_000_000_000u128),
|
|
|
|
];
|
|
|
|
|
|
|
|
let balances: (U256, U256, U256) = multicall.call().await.unwrap();
|
|
|
|
assert_eq!(balances.0, valid_balances[0]);
|
|
|
|
assert_eq!(balances.1, valid_balances[1]);
|
|
|
|
assert_eq!(balances.2, valid_balances[2]);
|
|
|
|
|
|
|
|
// call_array
|
|
|
|
multicall
|
|
|
|
.clear_calls()
|
|
|
|
.add_get_eth_balance(addrs[4], false)
|
|
|
|
.add_get_eth_balance(addrs[5], false)
|
|
|
|
.add_get_eth_balance(addrs[6], false);
|
|
|
|
|
|
|
|
let balances: Vec<U256> = multicall.call_array().await.unwrap();
|
|
|
|
assert_eq!(balances, Vec::from_iter(valid_balances.iter().copied()));
|
|
|
|
|
|
|
|
// clear multicall so we can test `call_raw` w/ >16 calls
|
|
|
|
multicall.clear_calls();
|
|
|
|
|
|
|
|
// clear the current value
|
|
|
|
simple_contract
|
|
|
|
.connect(client2.clone())
|
|
|
|
.method::<_, H256>("setValue", "many".to_owned())
|
|
|
|
.unwrap()
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
multicall.add_calls(
|
|
|
|
false,
|
|
|
|
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap()).take(17),
|
|
|
|
);
|
|
|
|
|
|
|
|
let tokens = multicall.call_raw().await.unwrap();
|
|
|
|
let results: Vec<String> = tokens
|
|
|
|
.into_iter()
|
|
|
|
.map(|result| {
|
|
|
|
// decode manually using Tokenizable method
|
|
|
|
String::from_token(result.unwrap()).unwrap()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
assert_eq!(results, ["many"; 17]);
|
|
|
|
|
|
|
|
// test version 2
|
|
|
|
multicall = multicall.version(MulticallVersion::Multicall2);
|
|
|
|
|
|
|
|
// deploy contract with reverting methods
|
|
|
|
let reverting_contract = {
|
|
|
|
let (abi, bytecode) =
|
|
|
|
compile_contract("SimpleRevertingStorage", "SimpleRevertingStorage.sol");
|
|
|
|
let f = ContractFactory::new(abi, bytecode, client.clone());
|
|
|
|
f.deploy("This contract can revert".to_string()).unwrap().send().await.unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
// reset value
|
|
|
|
reverting_contract
|
|
|
|
.connect(client2.clone())
|
|
|
|
.method::<_, H256>("setValue", ("reset third".to_owned(), false))
|
|
|
|
.unwrap()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// create calls
|
|
|
|
let set_value_call = reverting_contract
|
|
|
|
.connect(client.clone())
|
|
|
|
.method::<_, H256>("setValue", ("this didn't revert".to_owned(), false))
|
|
|
|
.unwrap();
|
|
|
|
let set_value_reverting_call = reverting_contract
|
|
|
|
.connect(client3.clone())
|
|
|
|
.method::<_, H256>("setValue", ("this reverted".to_owned(), true))
|
|
|
|
.unwrap();
|
|
|
|
let get_value_call =
|
|
|
|
reverting_contract.connect(client2.clone()).method::<_, String>("getValue", false).unwrap();
|
|
|
|
let get_value_reverting_call =
|
|
|
|
reverting_contract.connect(client.clone()).method::<_, String>("getValue", true).unwrap();
|
|
|
|
|
|
|
|
// .send reverts
|
|
|
|
// don't allow revert
|
|
|
|
multicall
|
|
|
|
.clear_calls()
|
|
|
|
.add_call(set_value_reverting_call.clone(), false)
|
|
|
|
.add_call(set_value_call.clone(), false);
|
|
|
|
multicall.send().await.unwrap_err();
|
|
|
|
|
|
|
|
// value has not changed
|
|
|
|
assert_eq!(get_value_call.clone().call().await.unwrap(), "reset third");
|
|
|
|
|
|
|
|
// allow revert
|
|
|
|
multicall
|
|
|
|
.clear_calls()
|
|
|
|
.add_call(set_value_reverting_call.clone(), true)
|
|
|
|
.add_call(set_value_call.clone(), false);
|
|
|
|
multicall.send().await.unwrap();
|
|
|
|
|
|
|
|
// value has changed
|
|
|
|
assert_eq!(get_value_call.clone().call().await.unwrap(), "this didn't revert");
|
|
|
|
|
|
|
|
// reset value again
|
|
|
|
reverting_contract
|
|
|
|
.connect(client2.clone())
|
|
|
|
.method::<_, H256>("setValue", ("reset third again".to_owned(), false))
|
|
|
|
.unwrap()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// .call reverts
|
|
|
|
// don't allow revert
|
|
|
|
multicall
|
|
|
|
.clear_calls()
|
|
|
|
.add_call(get_value_reverting_call.clone(), false)
|
|
|
|
.add_call(get_value_call.clone(), false);
|
|
|
|
let res = multicall.call::<(String, String)>().await;
|
|
|
|
let err = res.unwrap_err();
|
|
|
|
|
|
|
|
assert!(err.is_revert());
|
|
|
|
let message = err.decode_revert::<String>().unwrap();
|
|
|
|
assert!(message.contains("Multicall3: call failed"));
|
|
|
|
|
|
|
|
// allow revert -> call doesn't revert, but returns Err(_) in raw tokens
|
|
|
|
let expected = Bytes::from_static(b"getValue revert").encode();
|
|
|
|
multicall.clear_calls().add_call(get_value_reverting_call.clone(), true);
|
|
|
|
assert_eq!(multicall.call_raw().await.unwrap()[0].as_ref().unwrap_err()[4..], expected[..]);
|
|
|
|
assert_eq!(
|
|
|
|
multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap()[4..],
|
|
|
|
expected[..]
|
|
|
|
);
|
|
|
|
|
|
|
|
// v2 illegal revert
|
|
|
|
multicall
|
|
|
|
.clear_calls()
|
|
|
|
.add_call(get_value_reverting_call.clone(), false) // don't allow revert
|
|
|
|
.add_call(get_value_call.clone(), true); // true here will result in `tryAggregate(false, ...)`
|
|
|
|
assert!(matches!(
|
|
|
|
multicall.call::<(String, String)>().await.unwrap_err(),
|
|
|
|
MulticallError::IllegalRevert
|
|
|
|
));
|
|
|
|
|
|
|
|
// test version 3
|
|
|
|
// aggregate3 is the same as try_aggregate except with allowing failure on a per-call basis.
|
|
|
|
// no need to test that
|
|
|
|
multicall = multicall.version(MulticallVersion::Multicall3);
|
|
|
|
|
|
|
|
// .send with value
|
|
|
|
let amount = U256::from(100);
|
|
|
|
let value_tx = reverting_contract.method::<_, H256>("deposit", ()).unwrap().value(amount);
|
|
|
|
let rc_addr = reverting_contract.address();
|
|
|
|
|
|
|
|
let (bal_before,): (U256,) =
|
|
|
|
multicall.clear_calls().add_get_eth_balance(rc_addr, false).call().await.unwrap();
|
|
|
|
|
|
|
|
// send 2 value_tx
|
|
|
|
multicall.clear_calls().add_call(value_tx.clone(), false).add_call(value_tx.clone(), false);
|
|
|
|
multicall.send().await.unwrap();
|
|
|
|
|
|
|
|
let (bal_after,): (U256,) =
|
|
|
|
multicall.clear_calls().add_get_eth_balance(rc_addr, false).call().await.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(bal_after, bal_before + U256::from(2) * amount);
|
|
|
|
|
|
|
|
// test specific revert cases
|
|
|
|
// empty revert
|
|
|
|
let empty_revert = reverting_contract.method::<_, H256>("emptyRevert", ()).unwrap();
|
|
|
|
multicall.clear_calls().add_call(empty_revert.clone(), true);
|
|
|
|
assert!(multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap().is_empty());
|
|
|
|
|
|
|
|
// string revert
|
|
|
|
let string_revert =
|
|
|
|
reverting_contract.method::<_, H256>("stringRevert", "String".to_string()).unwrap();
|
|
|
|
multicall.clear_calls().add_call(string_revert, true);
|
|
|
|
assert_eq!(
|
|
|
|
multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap()[4..],
|
|
|
|
Bytes::from_static(b"String").encode()[..]
|
|
|
|
);
|
|
|
|
|
|
|
|
// custom error revert
|
|
|
|
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
|
|
|
multicall.clear_calls().add_call(custom_error, true);
|
|
|
|
assert_eq!(
|
|
|
|
multicall.call::<(Bytes,)>().await.unwrap_err().as_revert().unwrap()[..],
|
|
|
|
keccak256("CustomError()")[..4]
|
|
|
|
);
|
|
|
|
|
|
|
|
// custom error with data revert
|
|
|
|
let custom_error_with_data =
|
|
|
|
reverting_contract.method::<_, H256>("customErrorWithData", "Data".to_string()).unwrap();
|
|
|
|
multicall.clear_calls().add_call(custom_error_with_data, true);
|
|
|
|
let err = multicall.call::<(Bytes,)>().await.unwrap_err();
|
|
|
|
let bytes = err.as_revert().unwrap();
|
|
|
|
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
|
|
|
|
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
|
|
|
|
}
|
derive-eip712: initial implementation of eip712 derive macro (#481)
* derive-eip712: initial implementation of eip712 derive macro
This commit provides an initial implementation for a derive macro
to encode typed data according to EIP-712, https://eips.ethereum.org/EIPS/eip-712
Additionally, this commit introduces a new signer trait method:
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error>;
And implements the new method for each of the signers (wallet, ledger,
aws).
Additionally, these changes include using `WalletError` for the Wallet
signer error type
At the moment, derive does not recurse the primary type to find nested
Eip712 structs. This is something that is noted in the source and
currently responds with an error regarding custom types.
A subsequent PR should be opened once this issue becomes needed. For the
moment, the current implementation should satisfy non-nested, basic struct types.
* rename to ethers-derive-eip712; move to ethers-core
* refactor of derive-eip712 macro; use ParamType and EthAbiToken
* macro updates; add byte array checker for paramtype; use literal constant for domain type hash
* replace std::convert::Infallible with WalletError as Wallet signer error type
* update workspace members and dev dependencies for examples folder
* add example for eip712 and test against contract
* remove extraneous backward slash in '\x19\x01' prefix; example tests pass
* update unreleased change log
* remove print statements
* use parse_macro_input macro; remove dead code; handle nest struct not implemented error
* move eip712 example to solidity-contract tests folder; update cargo workspace dependencies
* allow optional EIP712Domain parameter when encoding eip712 struct and signing typed data
* add documentation for eip712 feature
* Update ethers-signers/src/ledger/mod.rs
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
* add error enum for Eip712Error; use sign_payload for ledger signer
* add EIP712WithDomain type for providing a wrapper around custom setting of the domain
* make LedgerWallet sign_payload public
* use optional feature gated dependencies for eip712; add default method for encode_eip712
* add default domain_separator method, pre-compute separator hash
* move derive-eip712 deps to dev deps
* remove invalid sign payload parameter, add await on async method
* remove deprecated comment
* debugging 'bad key handle' error for ledger signer
try using 'sign_message'
* await sign digest for aws signer
* remove extra space, fix fmt warning
* fix test, fmt errors
* use gt 0.6.0 pragma compiler version
* enable ABIEncoderV2 for solidity test contract
* chore: make test constructor public
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
2021-10-08 15:22:51 +00:00
|
|
|
|
2023-03-01 00:26:27 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_derive_eip712() {
|
|
|
|
// Generate Contract ABI Bindings
|
2023-03-18 18:46:17 +00:00
|
|
|
mod contract {
|
|
|
|
ethers_contract::abigen!(
|
|
|
|
DeriveEip712Test,
|
|
|
|
"./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
|
|
|
|
derives(serde::Deserialize, serde::Serialize)
|
|
|
|
);
|
|
|
|
}
|
2023-03-01 00:26:27 +00:00
|
|
|
|
|
|
|
// Create derived structs
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
|
|
|
#[eip712(
|
|
|
|
name = "Eip712Test",
|
|
|
|
version = "1",
|
|
|
|
chain_id = 1,
|
|
|
|
verifying_contract = "0x0000000000000000000000000000000000000001",
|
|
|
|
salt = "eip712-test-75F0CCte"
|
|
|
|
)]
|
|
|
|
struct FooBar {
|
|
|
|
foo: I256,
|
|
|
|
bar: U256,
|
|
|
|
fizz: Bytes,
|
|
|
|
buzz: [u8; 32],
|
|
|
|
far: String,
|
|
|
|
out: Address,
|
derive-eip712: initial implementation of eip712 derive macro (#481)
* derive-eip712: initial implementation of eip712 derive macro
This commit provides an initial implementation for a derive macro
to encode typed data according to EIP-712, https://eips.ethereum.org/EIPS/eip-712
Additionally, this commit introduces a new signer trait method:
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error>;
And implements the new method for each of the signers (wallet, ledger,
aws).
Additionally, these changes include using `WalletError` for the Wallet
signer error type
At the moment, derive does not recurse the primary type to find nested
Eip712 structs. This is something that is noted in the source and
currently responds with an error regarding custom types.
A subsequent PR should be opened once this issue becomes needed. For the
moment, the current implementation should satisfy non-nested, basic struct types.
* rename to ethers-derive-eip712; move to ethers-core
* refactor of derive-eip712 macro; use ParamType and EthAbiToken
* macro updates; add byte array checker for paramtype; use literal constant for domain type hash
* replace std::convert::Infallible with WalletError as Wallet signer error type
* update workspace members and dev dependencies for examples folder
* add example for eip712 and test against contract
* remove extraneous backward slash in '\x19\x01' prefix; example tests pass
* update unreleased change log
* remove print statements
* use parse_macro_input macro; remove dead code; handle nest struct not implemented error
* move eip712 example to solidity-contract tests folder; update cargo workspace dependencies
* allow optional EIP712Domain parameter when encoding eip712 struct and signing typed data
* add documentation for eip712 feature
* Update ethers-signers/src/ledger/mod.rs
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
* add error enum for Eip712Error; use sign_payload for ledger signer
* add EIP712WithDomain type for providing a wrapper around custom setting of the domain
* make LedgerWallet sign_payload public
* use optional feature gated dependencies for eip712; add default method for encode_eip712
* add default domain_separator method, pre-compute separator hash
* move derive-eip712 deps to dev deps
* remove invalid sign payload parameter, add await on async method
* remove deprecated comment
* debugging 'bad key handle' error for ledger signer
try using 'sign_message'
* await sign digest for aws signer
* remove extra space, fix fmt warning
* fix test, fmt errors
* use gt 0.6.0 pragma compiler version
* enable ABIEncoderV2 for solidity test contract
* chore: make test constructor public
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
2021-10-08 15:22:51 +00:00
|
|
|
}
|
2023-03-01 00:26:27 +00:00
|
|
|
|
|
|
|
// get ABI and bytecode for the DeriveEip712Test contract
|
|
|
|
let (abi, bytecode) = compile_contract("DeriveEip712Test", "DeriveEip712Test.sol");
|
|
|
|
|
|
|
|
// launch the network & connect to it
|
|
|
|
let anvil = Anvil::new().spawn();
|
|
|
|
let from = anvil.addresses()[0];
|
|
|
|
let provider = Provider::try_from(anvil.endpoint())
|
|
|
|
.unwrap()
|
|
|
|
.with_sender(from)
|
|
|
|
.interval(std::time::Duration::from_millis(10));
|
|
|
|
let client = Arc::new(provider);
|
|
|
|
|
|
|
|
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
|
|
|
|
|
|
|
let factory = ContractFactory::new(abi.clone(), bytecode.clone(), client.clone());
|
|
|
|
|
|
|
|
let contract = factory
|
|
|
|
.deploy(())
|
|
|
|
.expect("failed to deploy DeriveEip712Test contract")
|
|
|
|
.legacy()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.expect("failed to instantiate factory for DeriveEip712 contract");
|
|
|
|
|
|
|
|
let addr = contract.address();
|
|
|
|
|
2023-03-18 18:46:17 +00:00
|
|
|
let contract = contract::DeriveEip712Test::new(addr, client.clone());
|
2023-03-01 00:26:27 +00:00
|
|
|
|
|
|
|
let foo_bar = FooBar {
|
|
|
|
foo: I256::from(10u64),
|
|
|
|
bar: U256::from(20u64),
|
|
|
|
fizz: b"fizz".into(),
|
|
|
|
buzz: keccak256("buzz"),
|
|
|
|
far: String::from("space"),
|
|
|
|
out: Address::from([0; 20]),
|
|
|
|
};
|
|
|
|
|
2023-03-18 18:46:17 +00:00
|
|
|
let derived_foo_bar = contract::FooBar {
|
2023-03-01 00:26:27 +00:00
|
|
|
foo: foo_bar.foo,
|
|
|
|
bar: foo_bar.bar,
|
|
|
|
fizz: foo_bar.fizz.clone(),
|
|
|
|
buzz: foo_bar.buzz,
|
|
|
|
far: foo_bar.far.clone(),
|
|
|
|
out: foo_bar.out,
|
|
|
|
};
|
|
|
|
|
|
|
|
let sig = wallet.sign_typed_data(&foo_bar).await.expect("failed to sign typed data");
|
|
|
|
|
|
|
|
let r = <[u8; 32]>::try_from(sig.r)
|
|
|
|
.expect("failed to parse 'r' value from signature into [u8; 32]");
|
|
|
|
let s = <[u8; 32]>::try_from(sig.s)
|
|
|
|
.expect("failed to parse 's' value from signature into [u8; 32]");
|
|
|
|
let v = u8::try_from(sig.v).expect("failed to parse 'v' value from signature into u8");
|
|
|
|
|
|
|
|
let domain_separator = contract
|
|
|
|
.domain_separator()
|
|
|
|
.call()
|
|
|
|
.await
|
|
|
|
.expect("failed to retrieve domain_separator from contract");
|
|
|
|
let type_hash =
|
|
|
|
contract.type_hash().call().await.expect("failed to retrieve type_hash from contract");
|
|
|
|
let struct_hash = contract
|
|
|
|
.struct_hash(derived_foo_bar.clone())
|
|
|
|
.call()
|
|
|
|
.await
|
|
|
|
.expect("failed to retrieve struct_hash from contract");
|
|
|
|
let encoded = contract
|
|
|
|
.encode_eip_712(derived_foo_bar.clone())
|
|
|
|
.call()
|
|
|
|
.await
|
|
|
|
.expect("failed to retrieve eip712 encoded hash from contract");
|
|
|
|
let verify = contract
|
|
|
|
.verify_foo_bar(wallet.address(), derived_foo_bar, r, s, v)
|
|
|
|
.call()
|
|
|
|
.await
|
|
|
|
.expect("failed to verify signed typed data eip712 payload");
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
domain_separator,
|
|
|
|
foo_bar
|
|
|
|
.domain()
|
|
|
|
.expect("failed to return domain_separator from Eip712 implemented struct")
|
|
|
|
.separator(),
|
|
|
|
"domain separator does not match contract domain separator!"
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
type_hash,
|
|
|
|
FooBar::type_hash().expect("failed to return type_hash from Eip712 implemented struct"),
|
|
|
|
"type hash does not match contract struct type hash!"
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
struct_hash,
|
|
|
|
foo_bar
|
|
|
|
.clone()
|
|
|
|
.struct_hash()
|
|
|
|
.expect("failed to return struct_hash from Eip712 implemented struct"),
|
|
|
|
"struct hash does not match contract struct hash!"
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
encoded,
|
|
|
|
foo_bar
|
|
|
|
.encode_eip712()
|
|
|
|
.expect("failed to return domain_separator from Eip712 implemented struct"),
|
|
|
|
"Encoded value does not match!"
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(verify, "typed data signature failed!");
|
2020-06-17 09:22:01 +00:00
|
|
|
}
|