fix: serialize null filters

This commit is contained in:
Georgios Konstantopoulos 2020-06-03 00:10:46 +03:00
parent 701e442f94
commit b0feff2432
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
8 changed files with 67 additions and 38 deletions

View File

@ -57,11 +57,13 @@ impl Context {
cx.contract_name.to_string().to_lowercase() cx.contract_name.to_string().to_lowercase()
)); ));
let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase()));
// 0. Imports // 0. Imports
let imports = common::imports(); let imports = common::imports();
// 1. Declare Contract struct // 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 // 2. Declare events structs & impl FromTokens for each event
let events_decl = cx.events_declaration()?; let events_decl = cx.events_declaration()?;
@ -86,7 +88,7 @@ impl Context {
/// client at the given `Address`. The contract derefs to a `ethers::Contract` /// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object /// object
pub fn new<T: Into<Address>>(address: T, client: &'a Client<P, S>) -> Self { pub fn new<T: Into<Address>>(address: T, client: &'a Client<P, S>) -> Self {
let contract = Contract::new(address.into(), &ABI, client); let contract = Contract::new(address.into(), &#abi_name, client);
Self(contract) Self(contract)
} }

View File

@ -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 name = &cx.contract_name;
let abi = &cx.abi_str; let abi = &cx.abi_str;
quote! { quote! {
// Inline ABI declaration // Inline ABI declaration
static ABI: Lazy<Abi> = Lazy::new(|| serde_json::from_str(#abi) pub static #abi_name: Lazy<Abi> = Lazy::new(|| serde_json::from_str(#abi)
.expect("invalid abi")); .expect("invalid abi"));
// Struct declaration // Struct declaration

View File

@ -102,12 +102,13 @@ async fn deploy_and_call_contract() {
.event("ValueChanged") .event("ValueChanged")
.unwrap() .unwrap()
.from_block(0u64) .from_block(0u64)
.topic1(client.address()) // Corresponds to the first indexed parameter
.query() .query()
.await .await
.unwrap(); .unwrap();
assert_eq!(logs[0].new_value, "initial value"); assert_eq!(logs[0].new_value, "initial value");
assert_eq!(logs[1].new_value, "hi"); assert_eq!(logs[1].new_value, "hi2");
assert_eq!(logs[2].new_value, "hi2"); assert_eq!(logs.len(), 2);
let logs: Vec<ValueChanged> = contract2 let logs: Vec<ValueChanged> = contract2
.event("ValueChanged") .event("ValueChanged")
@ -123,19 +124,22 @@ async fn deploy_and_call_contract() {
// Note: We also provide the `abigen` macro for generating these bindings automatically // Note: We also provide the `abigen` macro for generating these bindings automatically
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct ValueChanged { struct ValueChanged {
author: Address, old_author: Address,
new_author: Address,
old_value: String, old_value: String,
new_value: String, new_value: String,
} }
impl Detokenize for ValueChanged { impl Detokenize for ValueChanged {
fn from_tokens(tokens: Vec<Token>) -> Result<ValueChanged, InvalidOutputType> { fn from_tokens(tokens: Vec<Token>) -> Result<ValueChanged, InvalidOutputType> {
let author: Address = tokens[0].clone().to_address().unwrap(); let old_author: Address = tokens[1].clone().to_address().unwrap();
let old_value = tokens[1].clone().to_string().unwrap(); let new_author: Address = tokens[1].clone().to_address().unwrap();
let new_value = tokens[2].clone().to_string().unwrap(); let old_value = tokens[2].clone().to_string().unwrap();
let new_value = tokens[3].clone().to_string().unwrap();
Ok(Self { Ok(Self {
author, old_author,
new_author,
old_value, old_value,
new_value, new_value,
}) })

View File

@ -2,13 +2,13 @@ pragma solidity >=0.4.24;
contract SimpleStorage { 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; address public lastSender;
string _value; string _value;
constructor(string memory value) public { constructor(string memory value) public {
emit ValueChanged(msg.sender, _value, value); emit ValueChanged(msg.sender, address(0), _value, value);
_value = value; _value = value;
} }
@ -17,7 +17,7 @@ contract SimpleStorage {
} }
function setValue(string memory value) public { function setValue(string memory value) public {
emit ValueChanged(msg.sender, _value, value); emit ValueChanged(msg.sender, lastSender, _value, value);
_value = value; _value = value;
lastSender = msg.sender; lastSender = msg.sender;
} }

View File

@ -3,7 +3,7 @@ use crate::{
types::{Address, BlockNumber, Bytes, H256, U256, U64}, types::{Address, BlockNumber, Bytes, H256, U256, U64},
utils::keccak256, utils::keccak256,
}; };
use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; use serde::{Deserialize, Serialize, Serializer};
use std::str::FromStr; use std::str::FromStr;
/// A log produced by a transaction. /// A log produced by a transaction.
@ -83,7 +83,6 @@ pub struct Filter {
/// Topics /// Topics
// TODO: We could improve the low level API here by using ethabi's RawTopicFilter // TODO: We could improve the low level API here by using ethabi's RawTopicFilter
// and/or TopicFilter // and/or TopicFilter
#[serde(serialize_with = "skip_nones")]
pub topics: [Option<ValueOrArray<H256>>; 4], pub topics: [Option<ValueOrArray<H256>>; 4],
/// Limit /// Limit
@ -202,24 +201,6 @@ where
} }
} }
// adapted from https://github.com/serde-rs/serde/issues/550#issuecomment-246746639
fn skip_nones<T, S>(elements: &[Option<T>], serializer: S) -> Result<S::Ok, S::Error>
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -4,7 +4,7 @@ use ethers_core::{
abi::{self, Detokenize, ParamType}, abi::{self, Detokenize, ParamType},
types::{ types::{
Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, NameOrAddress, Selector, Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, NameOrAddress, Selector,
Transaction, TransactionReceipt, TransactionRequest, TxHash, U256, Signature, Transaction, TransactionReceipt, TransactionRequest, TxHash, U256, U64,
}, },
utils, utils,
}; };
@ -40,7 +40,7 @@ impl<P: JsonRpcClient> Provider<P> {
// Functions for querying the state of the blockchain // Functions for querying the state of the blockchain
/// Gets the latest block number via the `eth_BlockNumber` API /// Gets the latest block number via the `eth_BlockNumber` API
pub async fn get_block_number(&self) -> Result<U256, ProviderError> { pub async fn get_block_number(&self) -> Result<U64, ProviderError> {
Ok(self Ok(self
.0 .0
.request("eth_blockNumber", None::<()>) .request("eth_blockNumber", None::<()>)
@ -258,6 +258,17 @@ impl<P: JsonRpcClient> Provider<P> {
.map_err(Into::into)?) .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<Signature, ProviderError> {
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 ////// Contract state
/// Returns an array (possibly empty) of logs that match the filter /// Returns an array (possibly empty) of logs that match the filter

View File

@ -1,6 +1,8 @@
use crate::Signer; 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 ethers_providers::{JsonRpcClient, Provider, ProviderError};
use std::ops::Deref; use std::ops::Deref;
@ -13,6 +15,7 @@ use thiserror::Error;
pub struct Client<P, S> { pub struct Client<P, S> {
pub(crate) provider: Provider<P>, pub(crate) provider: Provider<P>,
pub(crate) signer: Option<S>, pub(crate) signer: Option<S>,
pub(crate) address: Address,
} }
impl<P, S> From<Provider<P>> for Client<P, S> { impl<P, S> From<Provider<P>> for Client<P, S> {
@ -20,6 +23,7 @@ impl<P, S> From<Provider<P>> for Client<P, S> {
Client { Client {
provider, provider,
signer: None, signer: None,
address: Address::zero(),
} }
} }
} }
@ -36,11 +40,23 @@ pub enum ClientError {
EnsError(String), EnsError(String),
} }
// Helper functions for locally signing transactions
impl<P, S> Client<P, S> impl<P, S> Client<P, S>
where where
S: Signer, S: Signer,
P: JsonRpcClient, 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<T: Into<Bytes>>(&self, msg: T) -> Result<Signature, ClientError> {
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 /// Signs and broadcasts the transaction
pub async fn send_transaction( pub async fn send_transaction(
&self, &self,
@ -124,10 +140,23 @@ where
self.signer.as_ref().expect("no signer is configured") 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.signer = Some(signer);
self self
} }
/// Sets the provider
pub fn with_provider(&mut self, provider: Provider<P>) -> &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. // Abuse Deref to use the Provider's methods without re-writing everything.

View File

@ -64,7 +64,9 @@ impl Wallet {
/// Connects to a provider and returns a client /// Connects to a provider and returns a client
pub fn connect<P: JsonRpcClient>(self, provider: Provider<P>) -> Client<P, Wallet> { pub fn connect<P: JsonRpcClient>(self, provider: Provider<P>) -> Client<P, Wallet> {
let address = self.address();
Client { Client {
address,
signer: Some(self), signer: Some(self),
provider, provider,
} }