diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 70c66279..a2ea4a13 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -57,11 +57,13 @@ impl Context { cx.contract_name.to_string().to_lowercase() )); + let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase())); + // 0. Imports let imports = common::imports(); // 1. Declare Contract struct - let struct_decl = common::struct_declaration(&cx); + let struct_decl = common::struct_declaration(&cx, &abi_name); // 2. Declare events structs & impl FromTokens for each event let events_decl = cx.events_declaration()?; @@ -86,7 +88,7 @@ impl Context { /// client at the given `Address`. The contract derefs to a `ethers::Contract` /// object pub fn new>(address: T, client: &'a Client) -> Self { - let contract = Contract::new(address.into(), &ABI, client); + let contract = Contract::new(address.into(), &#abi_name, client); Self(contract) } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs index b12888b4..72e78cdf 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs @@ -20,13 +20,13 @@ pub(crate) fn imports() -> TokenStream { } } -pub(crate) fn struct_declaration(cx: &Context) -> TokenStream { +pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> TokenStream { let name = &cx.contract_name; let abi = &cx.abi_str; quote! { // Inline ABI declaration - static ABI: Lazy = Lazy::new(|| serde_json::from_str(#abi) + pub static #abi_name: Lazy = Lazy::new(|| serde_json::from_str(#abi) .expect("invalid abi")); // Struct declaration diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index bae981e9..df4bddbd 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -102,12 +102,13 @@ async fn deploy_and_call_contract() { .event("ValueChanged") .unwrap() .from_block(0u64) + .topic1(client.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[2].new_value, "hi2"); + assert_eq!(logs[1].new_value, "hi2"); + assert_eq!(logs.len(), 2); let logs: Vec = contract2 .event("ValueChanged") @@ -123,19 +124,22 @@ async fn deploy_and_call_contract() { // Note: We also provide the `abigen` macro for generating these bindings automatically #[derive(Clone, Debug)] struct ValueChanged { - author: Address, + old_author: Address, + new_author: Address, old_value: String, new_value: String, } impl Detokenize for ValueChanged { fn from_tokens(tokens: Vec) -> Result { - let author: Address = tokens[0].clone().to_address().unwrap(); - let old_value = tokens[1].clone().to_string().unwrap(); - let new_value = tokens[2].clone().to_string().unwrap(); + let old_author: Address = tokens[1].clone().to_address().unwrap(); + let new_author: Address = tokens[1].clone().to_address().unwrap(); + let old_value = tokens[2].clone().to_string().unwrap(); + let new_value = tokens[3].clone().to_string().unwrap(); Ok(Self { - author, + old_author, + new_author, old_value, new_value, }) diff --git a/ethers-contract/tests/contract.sol b/ethers-contract/tests/contract.sol index 306e5fba..b9193b15 100644 --- a/ethers-contract/tests/contract.sol +++ b/ethers-contract/tests/contract.sol @@ -2,13 +2,13 @@ pragma solidity >=0.4.24; contract SimpleStorage { - event ValueChanged(address indexed author, string oldValue, string newValue); + event ValueChanged(address indexed author, address indexed oldAuthor, string oldValue, string newValue); address public lastSender; string _value; constructor(string memory value) public { - emit ValueChanged(msg.sender, _value, value); + emit ValueChanged(msg.sender, address(0), _value, value); _value = value; } @@ -17,7 +17,7 @@ contract SimpleStorage { } function setValue(string memory value) public { - emit ValueChanged(msg.sender, _value, value); + emit ValueChanged(msg.sender, lastSender, _value, value); _value = value; lastSender = msg.sender; } diff --git a/ethers-core/src/types/chainstate/log.rs b/ethers-core/src/types/chainstate/log.rs index d12a1234..6d03e561 100644 --- a/ethers-core/src/types/chainstate/log.rs +++ b/ethers-core/src/types/chainstate/log.rs @@ -3,7 +3,7 @@ use crate::{ types::{Address, BlockNumber, Bytes, H256, U256, U64}, utils::keccak256, }; -use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use std::str::FromStr; /// A log produced by a transaction. @@ -83,7 +83,6 @@ pub struct Filter { /// Topics // TODO: We could improve the low level API here by using ethabi's RawTopicFilter // and/or TopicFilter - #[serde(serialize_with = "skip_nones")] pub topics: [Option>; 4], /// Limit @@ -202,24 +201,6 @@ where } } -// adapted from https://github.com/serde-rs/serde/issues/550#issuecomment-246746639 -fn skip_nones(elements: &[Option], serializer: S) -> Result -where - T: Serialize, - S: Serializer, -{ - // get number of Some elements - let len = elements.iter().filter(|opt| opt.is_some()).count(); - - let mut seq = serializer.serialize_seq(Some(len))?; - for elem in elements { - if elem.is_some() { - seq.serialize_element(elem)?; - } - } - seq.end() -} - #[cfg(test)] mod tests { use super::*; diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 2f50973e..885ab408 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -4,7 +4,7 @@ use ethers_core::{ abi::{self, Detokenize, ParamType}, types::{ Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, NameOrAddress, Selector, - Transaction, TransactionReceipt, TransactionRequest, TxHash, U256, + Signature, Transaction, TransactionReceipt, TransactionRequest, TxHash, U256, U64, }, utils, }; @@ -40,7 +40,7 @@ impl Provider

{ // Functions for querying the state of the blockchain /// Gets the latest block number via the `eth_BlockNumber` API - pub async fn get_block_number(&self) -> Result { + pub async fn get_block_number(&self) -> Result { Ok(self .0 .request("eth_blockNumber", None::<()>) @@ -258,6 +258,17 @@ impl Provider

{ .map_err(Into::into)?) } + /// Signs data using a specific account. This account needs to be unlocked. + pub async fn sign(&self, data: &Bytes, from: &Address) -> Result { + let data = utils::serialize(data); + let from = utils::serialize(from); + Ok(self + .0 + .request("eth_sign", Some(vec![from, data])) + .await + .map_err(Into::into)?) + } + ////// Contract state /// Returns an array (possibly empty) of logs that match the filter diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 3306b0fd..c30330ca 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -1,6 +1,8 @@ use crate::Signer; -use ethers_core::types::{Address, BlockNumber, NameOrAddress, TransactionRequest, TxHash}; +use ethers_core::types::{ + Address, BlockNumber, Bytes, NameOrAddress, Signature, TransactionRequest, TxHash, +}; use ethers_providers::{JsonRpcClient, Provider, ProviderError}; use std::ops::Deref; @@ -13,6 +15,7 @@ use thiserror::Error; pub struct Client { pub(crate) provider: Provider

, pub(crate) signer: Option, + pub(crate) address: Address, } impl From> for Client { @@ -20,6 +23,7 @@ impl From> for Client { Client { provider, signer: None, + address: Address::zero(), } } } @@ -36,11 +40,23 @@ pub enum ClientError { EnsError(String), } +// Helper functions for locally signing transactions impl Client where S: Signer, P: JsonRpcClient, { + /// Signs a message with the internal signer, or if none is present it will make a call to + /// the connected node's `eth_call` API. + pub async fn sign_message>(&self, msg: T) -> Result { + let msg = msg.into(); + Ok(if let Some(ref signer) = self.signer { + signer.sign_message(msg) + } else { + self.provider.sign(&msg, &self.address).await? + }) + } + /// Signs and broadcasts the transaction pub async fn send_transaction( &self, @@ -124,10 +140,23 @@ where self.signer.as_ref().expect("no signer is configured") } - pub fn with_signer(mut self, signer: S) -> Self { + /// Sets the signer + pub fn with_signer(&mut self, signer: S) -> &mut Self { self.signer = Some(signer); self } + + /// Sets the provider + pub fn with_provider(&mut self, provider: Provider

) -> &mut Self { + self.provider = provider; + self + } + + /// Sets the account to be used with the `eth_sign` API calls + pub fn from(&mut self, address: Address) -> &mut Self { + self.address = address; + self + } } // Abuse Deref to use the Provider's methods without re-writing everything. diff --git a/ethers-signers/src/wallet.rs b/ethers-signers/src/wallet.rs index b2b2f924..03f78164 100644 --- a/ethers-signers/src/wallet.rs +++ b/ethers-signers/src/wallet.rs @@ -64,7 +64,9 @@ impl Wallet { /// Connects to a provider and returns a client pub fn connect(self, provider: Provider

) -> Client { + let address = self.address(); Client { + address, signer: Some(self), provider, }