fix(abigen): correctly parse params in human readable abi (#194)

* fix(abigen): correctly parse params in human readable abi

* chore: make clippy happy

* test: remove unwrap

* chore: make clippy happy again

* chore: fix contract.rs example

* chore: rename to contract using human readable format

* examples: add abigen example with path to abi

* fix: pin funty version to fix bitvec error

* chore: remove unused import

* chore: fix deps
This commit is contained in:
Georgios Konstantopoulos 2021-02-16 19:10:26 +02:00 committed by GitHub
parent 457c646511
commit a43299c838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 110 additions and 41 deletions

1
Cargo.lock generated
View File

@ -681,6 +681,7 @@ dependencies = [
"ethabi-next", "ethabi-next",
"ethereum-types", "ethereum-types",
"ethers", "ethers",
"funty",
"generic-array", "generic-array",
"glob", "glob",
"hex", "hex",

View File

@ -107,7 +107,7 @@ impl Context {
// heuristic for parsing the human readable format // heuristic for parsing the human readable format
// replace bad chars // replace bad chars
let abi_str = abi_str.replace('[', "").replace(']', "").replace(',', ""); let abi_str = abi_str.replace('[', "").replace(']', "");
// split lines and get only the non-empty things // split lines and get only the non-empty things
let split: Vec<&str> = abi_str let split: Vec<&str> = abi_str
.split('\n') .split('\n')

View File

@ -36,7 +36,7 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
} else { } else {
quote! { quote! {
pub static #abi_name: Lazy<Abi> = Lazy::new(|| { pub static #abi_name: Lazy<Abi> = Lazy::new(|| {
let abi_str = #abi.replace('[', "").replace(']', "").replace(',', ""); let abi_str = #abi.replace('[', "").replace(']', "");
// split lines and get only the non-empty things // split lines and get only the non-empty things
let split: Vec<&str> = abi_str let split: Vec<&str> = abi_str
.split("\n") .split("\n")

View File

@ -30,7 +30,7 @@ impl Context {
.abi .abi
.events() .events()
.map(|event| expand_filter(event)) .map(|event| expand_filter(event))
.collect::<Result<Vec<_>>>()?; .collect::<Vec<_>>();
if data_types.is_empty() { if data_types.is_empty() {
return Ok(quote! {}); return Ok(quote! {});
@ -43,7 +43,7 @@ impl Context {
} }
/// Expands into a single method for contracting an event stream. /// Expands into a single method for contracting an event stream.
fn expand_filter(event: &Event) -> Result<TokenStream> { fn expand_filter(event: &Event) -> TokenStream {
// append `filter` to disambiguate with potentially conflicting // append `filter` to disambiguate with potentially conflicting
// function names // function names
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case())); let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
@ -53,13 +53,13 @@ fn expand_filter(event: &Event) -> Result<TokenStream> {
let ev_name = Literal::string(&event.name); let ev_name = Literal::string(&event.name);
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name)); let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
Ok(quote! { quote! {
#doc #doc
pub fn #name(&self) -> Event<M, #result> { pub fn #name(&self) -> Event<M, #result> {
self.0.event(#ev_name).expect("event not found (this should never happen)") self.0.event(#ev_name).expect("event not found (this should never happen)")
} }
}) }
} }
/// Expands an ABI event into a single event data type. This can expand either /// Expands an ABI event into a single event data type. This can expand either
@ -317,7 +317,7 @@ mod tests {
anonymous: false, anonymous: false,
}; };
assert_quote!(expand_filter(&event).unwrap(), { assert_quote!(expand_filter(&event), {
#[doc = "Gets the contract's `Transfer` event"] #[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_filter(&self) -> Event<M, TransferFilter> { pub fn transfer_filter(&self) -> Event<M, TransferFilter> {
self.0 self.0

View File

@ -32,12 +32,17 @@ glob = { version = "0.3.0", default-features = false }
bytes = { version = "1.0.1", features = ["serde"] } bytes = { version = "1.0.1", features = ["serde"] }
hex = { version = "0.4.2", default-features = false, features = ["std"] } hex = { version = "0.4.2", default-features = false, features = ["std"] }
# bitvec compilation issue
# https://github.com/bitvecto-rs/bitvec/issues/105#issuecomment-778570981
funty = "=1.1.0"
[dev-dependencies] [dev-dependencies]
ethers = { version = "0.2", path = "../ethers" } ethers = { version = "0.2", path = "../ethers" }
serde_json = { version = "1.0.62", default-features = false } serde_json = { version = "1.0.62", default-features = false }
bincode = { version = "1.2.1", default-features = false } bincode = { version = "1.2.1", default-features = false }
once_cell = { version = "1.5.2", default-features = false } once_cell = { version = "1.5.2" }
[features] [features]

View File

@ -248,8 +248,7 @@ where
mut tx: TransactionRequest, mut tx: TransactionRequest,
block: Option<BlockNumber>, block: Option<BlockNumber>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
if let Some(ref to) = tx.to { if let Some(NameOrAddress::Name(ens_name)) = tx.to {
if let NameOrAddress::Name(ens_name) = to {
let addr = self let addr = self
.inner .inner
.resolve_name(&ens_name) .resolve_name(&ens_name)
@ -257,7 +256,6 @@ where
.map_err(SignerMiddlewareError::MiddlewareError)?; .map_err(SignerMiddlewareError::MiddlewareError)?;
tx.to = Some(addr.into()) tx.to = Some(addr.into())
} }
}
// fill any missing fields // fill any missing fields
self.fill_transaction(&mut tx, block).await?; self.fill_transaction(&mut tx, block).await?;

View File

@ -59,8 +59,7 @@ where
block: Option<BlockNumber>, block: Option<BlockNumber>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
// resolve the to field if that's an ENS name. // resolve the to field if that's an ENS name.
if let Some(ref to) = tx.to { if let Some(NameOrAddress::Name(ens_name)) = tx.to {
if let NameOrAddress::Name(ens_name) = to {
let addr = self let addr = self
.inner .inner
.resolve_name(&ens_name) .resolve_name(&ens_name)
@ -68,7 +67,6 @@ where
.map_err(TransformerMiddlewareError::MiddlewareError)?; .map_err(TransformerMiddlewareError::MiddlewareError)?;
tx.to = Some(addr.into()) tx.to = Some(addr.into())
} }
}
// construct the appropriate proxy tx. // construct the appropriate proxy tx.
let proxy_tx = self.transformer.transform(tx)?; let proxy_tx = self.transformer.transform(tx)?;

View File

@ -73,15 +73,11 @@ impl<'a, P: JsonRpcClient> Future for PendingTransaction<'a, P> {
ctx.waker().wake_by_ref(); ctx.waker().wake_by_ref();
} }
PendingTxState::GettingReceipt(fut) => { PendingTxState::GettingReceipt(fut) => {
if let Ok(receipt) = futures_util::ready!(fut.as_mut().poll(ctx)) { if let Ok(Some(receipt)) = futures_util::ready!(fut.as_mut().poll(ctx)) {
if let Some(receipt) = receipt {
*this.state = PendingTxState::CheckingReceipt(Box::new(receipt)) *this.state = PendingTxState::CheckingReceipt(Box::new(receipt))
} else { } else {
*this.state = PendingTxState::PausedGettingReceipt *this.state = PendingTxState::PausedGettingReceipt
} }
} else {
*this.state = PendingTxState::PausedGettingReceipt
}
ctx.waker().wake_by_ref(); ctx.waker().wake_by_ref();
} }
PendingTxState::CheckingReceipt(receipt) => { PendingTxState::CheckingReceipt(receipt) => {

View File

@ -293,15 +293,13 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
tx.gas = Some(self.estimate_gas(&tx).await?); tx.gas = Some(self.estimate_gas(&tx).await?);
} }
if let Some(ref to) = tx.to { if let Some(NameOrAddress::Name(ref ens_name)) = tx.to {
if let NameOrAddress::Name(ens_name) = to {
// resolve to an address // resolve to an address
let addr = self.resolve_name(&ens_name).await?; let addr = self.resolve_name(&ens_name).await?;
// set the value // set the value
tx.to = Some(addr.into()) tx.to = Some(addr.into())
} }
}
let tx_hash = self.request("eth_sendTransaction", [tx]).await?; let tx_hash = self.request("eth_sendTransaction", [tx]).await?;

View File

@ -0,0 +1 @@
[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]

View File

@ -12,7 +12,7 @@ abigen!(
r#"[ r#"[
function setValue(string) function setValue(string)
function getValue() external view (string) function getValue() external view (string)
event ValueChanged(address indexed author, address indexed oldAuthor, string oldValue, string newValue) event ValueChanged(address indexed author, string oldValue, string newValue)
]"#, ]"#,
event_derives(serde::Deserialize, serde::Serialize) event_derives(serde::Deserialize, serde::Serialize)
); );
@ -20,7 +20,7 @@ abigen!(
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
// 1. compile the contract (note this requires that you are inside the `ethers/examples` directory) // 1. compile the contract (note this requires that you are inside the `ethers/examples` directory)
let compiled = Solc::new("./contract.sol").build()?; let compiled = Solc::new("**/contract.sol").build()?;
let contract = compiled let contract = compiled
.get("SimpleStorage") .get("SimpleStorage")
.expect("could not find contract"); .expect("could not find contract");

View File

@ -0,0 +1,72 @@
use anyhow::Result;
use ethers::{
prelude::*,
utils::{Ganache, Solc},
};
use std::{convert::TryFrom, sync::Arc, time::Duration};
// Generate the type-safe contract bindings by providing the ABI
// definition in human readable format
abigen!(
SimpleContract,
"./ethers/examples/contract_abi.json",
event_derives(serde::Deserialize, serde::Serialize)
);
#[tokio::main]
async fn main() -> Result<()> {
// 1. compile the contract (note this requires that you are inside the `ethers/examples` directory)
let compiled = Solc::new("**/contract.sol").build()?;
let contract = compiled
.get("SimpleStorage")
.expect("could not find contract");
dbg!("OK");
// 2. launch ganache
let ganache = Ganache::new().spawn();
// 3. instantiate our wallet
let wallet: LocalWallet = ganache.keys()[0].clone().into();
// 4. connect to the network
let provider =
Provider::<Http>::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64));
// 5. instantiate the client with the wallet
let client = SignerMiddleware::new(provider, wallet);
let client = Arc::new(client);
// 6. create a factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(
contract.abi.clone(),
contract.bytecode.clone(),
client.clone(),
);
// 7. deploy it with the constructor arguments
let contract = factory.deploy("initial value".to_string())?.send().await?;
// 8. get the contract's address
let addr = contract.address();
// 9. instantiate the contract
let contract = SimpleContract::new(addr, client.clone());
// 10. call the `setValue` method
// (first `await` returns a PendingTransaction, second one waits for it to be mined)
let _receipt = contract.set_value("hi".to_owned()).send().await?.await?;
// 11. get all events
let logs = contract
.value_changed_filter()
.from_block(0u64)
.query()
.await?;
// 12. get the new value
let value = contract.get_value().call().await?;
println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
Ok(())
}