From b6b5b09f4ab437d1bcae2b732b7d3269429c97f7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 19 Mar 2022 05:23:33 +0100 Subject: [PATCH] feat(abigen): add abi object deserializer and generate deploy function (#1030) * feat(abigen): add abi object deserializer * chore: rustfmt * refactor: use enum type for deser abi * refactor: use enum types for deser * chore: rustfmt * feat: add bytecode field * feat: generate bytecode static * feat: generate deployment function * refactor: deploy function * feat: add contract deployer type * feat: make 0x prefix optional * feat: add deploy function * feat: add deploy example * chore: update CHANGELOG * chore(clippy): make clippy happy --- CHANGELOG.md | 2 + .../ethers-contract-abigen/src/contract.rs | 97 +++++++++---- .../src/contract/common.rs | 18 ++- .../src/contract/methods.rs | 55 ++++++++ .../ethers-contract-abigen/src/rawabi.rs | 129 ++++++++++++++++++ ethers-contract/src/factory.rs | 80 ++++++++++- ethers-contract/src/lib.rs | 9 +- ethers-contract/tests/abigen.rs | 18 +++ .../tests/solidity-contracts/greeter.json | 46 +++++++ ethers-core/src/types/bytes.rs | 29 ++-- ethers-core/src/types/mod.rs | 2 +- ethers-solc/src/artifacts/serde_helpers.rs | 9 +- examples/contract_with_abi.rs | 2 +- examples/contract_with_abi_and_bytecode.rs | 36 +++++ 14 files changed, 469 insertions(+), 63 deletions(-) create mode 100644 ethers-contract/tests/solidity-contracts/greeter.json create mode 100644 examples/contract_with_abi_and_bytecode.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b34a53d8..12f6b1d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,8 @@ ### Unreleased +- Generate a deploy function if bytecode is provided in the abigen! input (json artifact) + [#1030](https://github.com/gakonst/ethers-rs/pull/1030). - Generate correct bindings of struct's field names that are reserved words [#989](https://github.com/gakonst/ethers-rs/pull/989). diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index c9652f3d..2dd4a4fa 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -6,7 +6,7 @@ mod structs; mod types; use super::{util, Abigen}; -use crate::{contract::structs::InternalStructs, rawabi::RawAbi}; +use crate::contract::structs::InternalStructs; use ethers_core::{ abi::{Abi, AbiParser}, macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}, @@ -14,6 +14,9 @@ use ethers_core::{ use eyre::{eyre, Context as _, Result}; use crate::contract::methods::MethodAlias; + +use crate::rawabi::JsonAbi; +use ethers_core::types::Bytes; use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; use serde::Deserialize; @@ -89,6 +92,9 @@ pub struct Context { /// Manually specified event aliases. event_aliases: BTreeMap, + + /// Bytecode extracted from the abi string input, if present. + contract_bytecode: Option, } impl Context { @@ -97,14 +103,13 @@ impl Context { let name = &self.contract_ident; let name_mod = util::ident(&format!("{}_mod", self.contract_ident.to_string().to_lowercase())); - - let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase())); + let abi_name = self.inline_abi_ident(); // 0. Imports let imports = common::imports(&name.to_string()); // 1. Declare Contract struct - let struct_decl = common::struct_declaration(self, &abi_name); + let struct_decl = common::struct_declaration(self); // 2. Declare events structs & impl FromTokens for each event let events_decl = self.events_declaration()?; @@ -115,7 +120,10 @@ impl Context { // 4. impl block for the contract methods and their corresponding types let (contract_methods, call_structs) = self.methods_and_call_structs()?; - // 5. Declare the structs parsed from the human readable abi + // 5. generate deploy function if + let deployment_methods = self.deployment_methods(); + + // 6. Declare the structs parsed from the human readable abi let abi_structs_decl = self.abi_structs()?; let ethers_core = ethers_core_crate(); @@ -130,16 +138,21 @@ impl Context { /// client at the given `Address`. The contract derefs to a `ethers::Contract` /// object pub fn new>(address: T, client: ::std::sync::Arc) -> Self { - let contract = #ethers_contract::Contract::new(address.into(), #abi_name.clone(), client); - Self(contract) + #ethers_contract::Contract::new(address.into(), #abi_name.clone(), client).into() } - // TODO: Implement deployment. + #deployment_methods #contract_methods #contract_events } + + impl From<#ethers_contract::Contract> for #name { + fn from(contract: #ethers_contract::Contract) -> Self { + Self(contract) + } + } }; Ok(ExpandedContract { @@ -158,6 +171,9 @@ impl Context { let mut abi_str = args.abi_source.get().map_err(|e| eyre!("failed to get ABI JSON: {}", e))?; + // holds the bytecode parsed from the abi_str, if present + let mut contract_bytecode = None; + let (abi, human_readable, abi_parser) = parse_abi(&abi_str)?; // try to extract all the solidity structs from the normal JSON ABI @@ -175,22 +191,17 @@ impl Context { internal_structs.outputs = abi_parser.outputs.clone(); internal_structs - } else if abi_str.starts_with('{') { - if let Ok(abi) = serde_json::from_str::(&abi_str) { - if let Ok(s) = serde_json::to_string(&abi) { + } else { + match serde_json::from_str::(&abi_str)? { + JsonAbi::Object(obj) => { // need to update the `abi_str` here because we only want the `"abi": [...]` // part of the json object in the contract binding - abi_str = s; + abi_str = serde_json::to_string(&obj.abi)?; + contract_bytecode = obj.bytecode; + InternalStructs::new(obj.abi) } - InternalStructs::new(abi) - } else { - InternalStructs::default() + JsonAbi::Array(abi) => InternalStructs::new(abi), } - } else { - serde_json::from_str::(&abi_str) - .ok() - .map(InternalStructs::new) - .unwrap_or_default() }; let contract_ident = util::ident(&args.contract_name); @@ -231,6 +242,7 @@ impl Context { internal_structs, contract_ident, contract_name: args.contract_name, + contract_bytecode, method_aliases, event_derives, event_aliases, @@ -242,6 +254,16 @@ impl Context { &self.contract_name } + /// name of the `Lazy` that stores the ABI + pub(crate) fn inline_abi_ident(&self) -> Ident { + util::safe_ident(&format!("{}_ABI", self.contract_ident.to_string().to_uppercase())) + } + + /// name of the `Lazy` that stores the Bytecode + pub(crate) fn inline_bytecode_ident(&self) -> Ident { + util::safe_ident(&format!("{}_BYTECODE", self.contract_ident.to_string().to_uppercase())) + } + /// The internal abi struct mapping table pub fn internal_structs(&self) -> &InternalStructs { &self.internal_structs @@ -259,18 +281,33 @@ fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> { let res = if let Ok(abi) = abi_parser.parse_str(abi_str) { (abi, true, abi_parser) } else { - #[derive(Deserialize)] - struct Contract { - abi: Abi, - } // a best-effort coercion of an ABI or an artifact JSON into an artifact JSON. - let contract: Contract = if abi_str.trim_start().starts_with('[') { - serde_json::from_str(&format!(r#"{{"abi":{}}}"#, abi_str.trim()))? - } else { - serde_json::from_str::(abi_str)? - }; + let contract: JsonContract = serde_json::from_str(abi_str)?; - (contract.abi, false, abi_parser) + (contract.into_abi(), false, abi_parser) }; Ok(res) } + +#[derive(Deserialize)] +struct ContractObject { + abi: Abi, +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum JsonContract { + /// json object input as `{"abi": [..], "bin": "..."}` + Object(ContractObject), + /// json array input as `[]` + Array(Abi), +} + +impl JsonContract { + fn into_abi(self) -> Abi { + match self { + JsonContract::Object(o) => o.abi, + JsonContract::Array(abi) => abi, + } + } +} diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs index 07a008e7..818c4ab9 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs @@ -30,10 +30,12 @@ pub(crate) fn imports(name: &str) -> TokenStream { } /// Generates the static `Abi` constants and the contract struct -pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> TokenStream { +pub(crate) fn struct_declaration(cx: &Context) -> TokenStream { let name = &cx.contract_ident; let abi = &cx.abi_str; + let abi_name = cx.inline_abi_ident(); + let ethers_core = ethers_core_crate(); let ethers_providers = ethers_providers_crate(); let ethers_contract = ethers_contract_crate(); @@ -50,10 +52,24 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> } }; + let bytecode = if let Some(ref bytecode) = cx.contract_bytecode { + let bytecode_name = cx.inline_bytecode_ident(); + let hex_bytecode = format!("{}", bytecode); + quote! { + /// Bytecode of the #name contract + pub static #bytecode_name: #ethers_contract::Lazy<#ethers_core::types::Bytes> = #ethers_contract::Lazy::new(|| #hex_bytecode.parse() + .expect("invalid bytecode")); + } + } else { + quote! {} + }; + quote! { // Inline ABI declaration #abi_parse + #bytecode + // Struct declaration #[derive(Clone)] pub struct #name(#ethers_contract::Contract); diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 4d54aea2..a9ca643a 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -42,6 +42,61 @@ impl Context { Ok((function_impls, call_structs)) } + /// Returns all deploy (constructor) implementations + pub(crate) fn deployment_methods(&self) -> TokenStream { + if self.contract_bytecode.is_some() { + let ethers_core = ethers_core_crate(); + let ethers_contract = ethers_contract_crate(); + + let abi_name = self.inline_abi_ident(); + let get_abi = quote! { + #abi_name.clone() + }; + + let bytecode_name = self.inline_bytecode_ident(); + let get_bytecode = quote! { + #bytecode_name.clone().into() + }; + + let deploy = quote! { + /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it. + /// Returns a new instance of a deployer that returns an instance of this contract after sending the transaction + /// + /// Notes: + /// 1. If there are no constructor arguments, you should pass `()` as the argument. + /// 1. The default poll duration is 7 seconds. + /// 1. The default number of confirmations is 1 block. + /// + /// + /// # Example + /// + /// Generate contract bindings with `abigen!` and deploy a new contract instance. + /// + /// *Note*: this requires a `bytecode` and `abi` object in the `greeter.json` artifact. + /// + /// ```ignore + /// # async fn deploy(client: ::std::sync::Arc) { + /// abigen!(Greeter,"../greeter.json"); + /// + /// let greeter_contract = Greeter::deploy(client, "Hello world!".to_string()).unwrap().send().await.unwrap(); + /// let msg = greeter_contract.greet().call().await.unwrap(); + /// # } + /// ``` + pub fn deploy(client: ::std::sync::Arc, constructor_args: T) -> Result<#ethers_contract::builders::ContractDeployer, #ethers_contract::ContractError> { + let factory = #ethers_contract::ContractFactory::new(#get_abi, #get_bytecode, client); + let deployer = factory.deploy(constructor_args)?; + let deployer = #ethers_contract::ContractDeployer::new(deployer); + Ok(deployer) + } + + }; + + return deploy + } + + quote! {} + } + /// Expands to the corresponding struct type based on the inputs of the given function fn expand_call_struct( &self, diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index 782e4d35..501ecc8a 100644 --- a/ethers-contract/ethers-contract-abigen/src/rawabi.rs +++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs @@ -2,6 +2,7 @@ //! raw content of the ABI. #![allow(missing_docs)] +use ethers_core::types::Bytes; use serde::{ de::{MapAccess, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize, @@ -105,11 +106,109 @@ pub struct Component { pub indexed: Option, } +/// Represents contract ABI input variants +#[derive(Deserialize)] +#[serde(untagged)] +pub(crate) enum JsonAbi { + /// json object input as `{"abi": [..], "bin": "..."}` + Object(AbiObject), + /// json array input as `[]` + #[serde(deserialize_with = "deserialize_abi_array")] + Array(RawAbi), +} + +fn deserialize_abi_array<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_seq(RawAbiVisitor) +} + +/// Contract ABI and optional bytecode as JSON object +pub(crate) struct AbiObject { + pub abi: RawAbi, + pub bytecode: Option, +} + +struct AbiObjectVisitor; + +impl<'de> Visitor<'de> for AbiObjectVisitor { + type Value = AbiObject; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence or map with `abi` key") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut abi = None; + let mut bytecode = None; + + #[derive(Deserialize)] + struct BytecodeObject { + object: Bytes, + } + + struct DeserializeBytes(Bytes); + + impl<'de> Deserialize<'de> for DeserializeBytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(DeserializeBytes(ethers_core::types::deserialize_bytes(deserializer)?.into())) + } + } + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "abi" => { + abi = Some(RawAbi(map.next_value::>()?)); + } + "bytecode" | "byteCode" => { + bytecode = map.next_value::().ok().map(|obj| obj.object); + } + "bin" => { + bytecode = map.next_value::().ok().map(|b| b.0); + } + _ => { + map.next_value::()?; + } + } + } + + let abi = abi.ok_or_else(|| serde::de::Error::missing_field("abi"))?; + Ok(AbiObject { abi, bytecode }) + } +} + +impl<'de> Deserialize<'de> for AbiObject { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(AbiObjectVisitor) + } +} + #[cfg(test)] mod tests { use super::*; use ethers_core::abi::Abi; + fn assert_has_bytecode(s: &str) { + match serde_json::from_str::(&s).unwrap() { + JsonAbi::Object(abi) => { + assert!(abi.bytecode.is_some()); + } + _ => { + panic!("expected abi object") + } + } + } + #[test] fn can_parse_raw_abi() { const VERIFIER_ABI: &str = include_str!("../../tests/solidity-contracts/verifier_abi.json"); @@ -140,4 +239,34 @@ mod tests { let de = serde_json::to_string(&raw).unwrap(); assert_eq!(abi, serde_json::from_str::(&de).unwrap()); } + + #[test] + fn can_deserialize_abi_object() { + let abi_str = r#"[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"number","type":"uint64"}],"name":"MyEvent","type":"event"},{"inputs":[],"name":"greet","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#; + let abi = serde_json::from_str::(abi_str).unwrap(); + assert!(matches!(abi, JsonAbi::Array(_))); + + let code = "0x608060405234801561001057600080fd5b50610242806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80635581701b14610030575b600080fd5b61004a60048036038101906100459190610199565b610060565b60405161005791906101f1565b60405180910390f35b610068610070565b819050919050565b60405180602001604052806000151581525090565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100e282610099565b810181811067ffffffffffffffff82111715610101576101006100aa565b5b80604052505050565b6000610114610085565b905061012082826100d9565b919050565b60008115159050919050565b61013a81610125565b811461014557600080fd5b50565b60008135905061015781610131565b92915050565b60006020828403121561017357610172610094565b5b61017d602061010a565b9050600061018d84828501610148565b60008301525092915050565b6000602082840312156101af576101ae61008f565b5b60006101bd8482850161015d565b91505092915050565b6101cf81610125565b82525050565b6020820160008201516101eb60008501826101c6565b50505050565b600060208201905061020660008301846101d5565b9291505056fea2646970667358221220890202b0964477379a457ab3725a21d7c14581e4596552e32a54e23f1c6564e064736f6c634300080c0033"; + let s = format!(r#"{{"abi": {}, "bin" : "{}" }}"#, abi_str, code); + assert_has_bytecode(&s); + + let s = format!(r#"{{"abi": {}, "bytecode" : {{ "object": "{}" }} }}"#, abi_str, code); + assert_has_bytecode(&s); + + let hh_artifact = include_str!("../../tests/solidity-contracts/verifier_abi_hardhat.json"); + match serde_json::from_str::(hh_artifact).unwrap() { + JsonAbi::Object(abi) => { + assert!(abi.bytecode.is_none()); + } + _ => { + panic!("expected abi object") + } + } + } + + #[test] + fn can_parse_greeter_bytecode() { + let artifact = include_str!("../../tests/solidity-contracts/greeter.json"); + assert_has_bytecode(artifact); + } } diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs index 9e7103ca..0df95da1 100644 --- a/ethers-contract/src/factory.rs +++ b/ethers-contract/src/factory.rs @@ -1,4 +1,5 @@ use crate::{Contract, ContractError}; +use std::marker::PhantomData; use ethers_core::{ abi::{Abi, Token, Tokenize}, @@ -14,8 +15,82 @@ use ethers_core::types::Eip1559TransactionRequest; use std::sync::Arc; +/// Helper which manages the deployment transaction of a smart contract. +/// +/// This is just a wrapper type for [Deployer] with an additional type to convert the [Contract] +/// that the deployer returns when sending the transaction. #[derive(Debug, Clone)] +#[must_use = "Deployer does nothing unless you `send` it"] +pub struct ContractDeployer { + /// the actual deployer + deployer: Deployer, + /// marker for the `Contract` type to create afterwards + /// + /// this type will be used to construct it via `From::from(Contract)` + _contract: PhantomData, +} + +impl>> ContractDeployer { + /// Create a new instance of this [ContractDeployer] + pub fn new(deployer: Deployer) -> Self { + Self { deployer, _contract: Default::default() } + } + + /// Sets the number of confirmations to wait for the contract deployment transaction + pub fn confirmations>(mut self, confirmations: T) -> Self { + self.deployer.confs = confirmations.into(); + self + } + + pub fn block>(mut self, block: T) -> Self { + self.deployer.block = block.into(); + self + } + + /// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment + pub fn legacy(mut self) -> Self { + self.deployer = self.deployer.legacy(); + self + } + + /// Dry runs the deployment of the contract + /// + /// Note: this function _does not_ send a transaction from your account + pub async fn call(&self) -> Result<(), ContractError> { + self.deployer.call().await + } + + /// Broadcasts the contract deployment transaction and after waiting for it to + /// be sufficiently confirmed (default: 1), it returns a new instance of the contract type at + /// the deployed contract's address. + pub async fn send(self) -> Result> { + let contract = self.deployer.send().await?; + Ok(C::from(contract)) + } + + /// Broadcasts the contract deployment transaction and after waiting for it to + /// be sufficiently confirmed (default: 1), it returns a new instance of the contract type at + /// the deployed contract's address and the corresponding + /// [`TransactionReceipt`](ethers_core::types::TransactionReceipt). + pub async fn send_with_receipt(self) -> Result<(C, TransactionReceipt), ContractError> { + let (contract, receipt) = self.deployer.send_with_receipt().await?; + Ok((C::from(contract), receipt)) + } + + /// Returns a reference to the deployer's ABI + pub fn abi(&self) -> &Abi { + self.deployer.abi() + } + + /// Returns a reference to the deployer's client + pub fn client(&self) -> &M { + self.deployer.client() + } +} + /// Helper which manages the deployment transaction of a smart contract +#[derive(Debug, Clone)] +#[must_use = "Deployer does nothing unless you `send` it"] pub struct Deployer { /// The deployer's transaction, exposed for overriding the defaults pub tx: TypedTransaction, @@ -27,20 +102,17 @@ pub struct Deployer { impl Deployer { /// Sets the number of confirmations to wait for the contract deployment transaction - #[must_use] pub fn confirmations>(mut self, confirmations: T) -> Self { self.confs = confirmations.into(); self } - #[must_use] pub fn block>(mut self, block: T) -> Self { self.block = block.into(); self } /// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment - #[must_use] pub fn legacy(mut self) -> Self { self.tx = match self.tx { TypedTransaction::Eip1559(inner) => { @@ -109,7 +181,6 @@ impl Deployer { } } -#[derive(Debug, Clone)] /// To deploy a contract to the Ethereum network, a `ContractFactory` can be /// created which manages the Contract bytecode and Application Binary Interface /// (ABI), usually generated from the Solidity compiler. @@ -151,6 +222,7 @@ impl Deployer { /// println!("{}", contract.address()); /// # Ok(()) /// # } +#[derive(Debug, Clone)] pub struct ContractFactory { client: Arc, abi: Abi, diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 98074b00..1d1532ac 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -10,7 +10,7 @@ mod call; pub use call::{ContractError, EthCall}; mod factory; -pub use factory::ContractFactory; +pub use factory::{ContractDeployer, ContractFactory}; mod event; pub use event::EthEvent; @@ -25,8 +25,13 @@ pub use multicall::Multicall; /// This module exposes low lever builder structures which are only consumed by the /// type-safe ABI bindings generators. +#[doc(hidden)] pub mod builders { - pub use super::{call::ContractCall, event::Event, factory::Deployer}; + pub use super::{ + call::ContractCall, + event::Event, + factory::{ContractDeployer, Deployer}, + }; } #[cfg(any(test, feature = "abigen"))] diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 9017633d..63faf133 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -478,6 +478,24 @@ fn can_handle_case_sensitive_calls() { let _ = contract.INDEX(); } +#[tokio::test] +async fn can_deploy_greeter() { + abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter.json",); + let ganache = ethers_core::utils::Ganache::new().spawn(); + let from = ganache.addresses()[0]; + let provider = Provider::try_from(ganache.endpoint()) + .unwrap() + .with_sender(from) + .interval(std::time::Duration::from_millis(10)); + let client = Arc::new(provider); + + let greeter_contract = + Greeter::deploy(client, "Hello World!".to_string()).unwrap().legacy().send().await.unwrap(); + + let greeting = greeter_contract.greet().call().await.unwrap(); + assert_eq!("Hello World!", greeting); +} + #[tokio::test] async fn can_abiencoderv2_output() { abigen!(AbiEncoderv2Test, "ethers-contract/tests/solidity-contracts/abiencoderv2test_abi.json",); diff --git a/ethers-contract/tests/solidity-contracts/greeter.json b/ethers-contract/tests/solidity-contracts/greeter.json new file mode 100644 index 00000000..1abacd12 --- /dev/null +++ b/ethers-contract/tests/solidity-contracts/greeter.json @@ -0,0 +1,46 @@ +{ + "bytecode": { + "object": "608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH2 0x491 CODESIZE SUB DUP1 PUSH2 0x491 DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x33 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD PUSH1 0x40 MLOAD SWAP4 SWAP3 SWAP2 SWAP1 DUP5 PUSH5 0x100000000 DUP3 GT ISZERO PUSH2 0x53 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 DUP4 ADD SWAP1 PUSH1 0x20 DUP3 ADD DUP6 DUP2 GT ISZERO PUSH2 0x68 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 MLOAD PUSH5 0x100000000 DUP2 GT DUP3 DUP3 ADD DUP9 LT OR ISZERO PUSH2 0x82 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 MSTORE POP DUP2 MLOAD PUSH1 0x20 SWAP2 DUP3 ADD SWAP3 SWAP1 SWAP2 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0xAF JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x97 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0xDC JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP PUSH1 0x40 MSTORE POP POP DUP2 MLOAD PUSH2 0xF6 SWAP2 POP PUSH1 0x0 SWAP1 PUSH1 0x20 DUP5 ADD SWAP1 PUSH2 0xFD JUMP JUMPDEST POP POP PUSH2 0x19E JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH2 0x133 JUMPI PUSH1 0x0 DUP6 SSTORE PUSH2 0x179 JUMP JUMPDEST DUP3 PUSH1 0x1F LT PUSH2 0x14C JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x179 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x179 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x179 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x15E JUMP JUMPDEST POP PUSH2 0x185 SWAP3 SWAP2 POP PUSH2 0x189 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x185 JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0x18A JUMP JUMPDEST PUSH2 0x2E4 DUP1 PUSH2 0x1AD PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x36 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xA4136862 EQ PUSH2 0x3B JUMPI DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0xE3 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xE1 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x51 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 PUSH1 0x20 DUP2 ADD DUP2 CALLDATALOAD PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x6C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x7E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0xA0 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 SWAP3 ADD SWAP2 SWAP1 SWAP2 MSTORE POP SWAP3 SWAP6 POP PUSH2 0x160 SWAP5 POP POP POP POP POP JUMP JUMPDEST STOP JUMPDEST PUSH2 0xEB PUSH2 0x177 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x125 JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x10D JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x152 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST DUP1 MLOAD PUSH2 0x173 SWAP1 PUSH1 0x0 SWAP1 PUSH1 0x20 DUP5 ADD SWAP1 PUSH2 0x20D JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 PUSH1 0x1F PUSH1 0x2 PUSH1 0x0 NOT PUSH2 0x100 PUSH1 0x1 DUP9 AND ISZERO MUL ADD SWAP1 SWAP6 AND SWAP5 SWAP1 SWAP5 DIV SWAP4 DUP5 ADD DUP2 SWAP1 DIV DUP2 MUL DUP3 ADD DUP2 ADD SWAP1 SWAP3 MSTORE DUP3 DUP2 MSTORE PUSH1 0x60 SWAP4 SWAP1 SWAP3 SWAP1 SWAP2 DUP4 ADD DUP3 DUP3 DUP1 ISZERO PUSH2 0x203 JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x1D8 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x203 JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x1E6 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH2 0x243 JUMPI PUSH1 0x0 DUP6 SSTORE PUSH2 0x289 JUMP JUMPDEST DUP3 PUSH1 0x1F LT PUSH2 0x25C JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x289 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x289 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x289 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x26E JUMP JUMPDEST POP PUSH2 0x295 SWAP3 SWAP2 POP PUSH2 0x299 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x295 JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0x29A JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 DUP12 SWAP2 PUSH2 0xDFD1 SWAP6 0xD5 CALLDATASIZE XOR SWAP5 0x2A PUSH19 0xA3B481D61A7B142DE919925A0B34F9C986E570 PUSH31 0x64736F6C634300070600330000000000000000000000000000000000000000 ", + "sourceMap": "70:316:0:-:0;;;123:74;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;123:74:0;;;;;;;;;;-1:-1:-1;123:74:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;123:74:0;;-1:-1:-1;;170:20:0;;;;-1:-1:-1;170:8:0;;:20;;;;;:::i;:::-;;123:74;70:316;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;70:316:0;;;-1:-1:-1;70:316:0;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;" + }, + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "greet", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "name": "setGreeting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/ethers-core/src/types/bytes.rs b/ethers-core/src/types/bytes.rs index 21f2c719..d8728362 100644 --- a/ethers-core/src/types/bytes.rs +++ b/ethers-core/src/types/bytes.rs @@ -1,7 +1,4 @@ -use serde::{ - de::{Error, Unexpected}, - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; use std::{ @@ -77,13 +74,13 @@ impl FromStr for Bytes { type Err = ParseBytesError; fn from_str(value: &str) -> Result { - if value.len() >= 2 && &value[0..2] == "0x" { - let bytes: Vec = hex::decode(&value[2..]) - .map_err(|e| ParseBytesError(format!("Invalid hex: {}", e)))?; - Ok(bytes.into()) + if let Some(value) = value.strip_prefix("0x") { + hex::decode(value) } else { - Err(ParseBytesError("Doesn't start with 0x".to_string())) + hex::decode(&value) } + .map(Into::into) + .map_err(|e| ParseBytesError(format!("Invalid hex: {}", e))) } } @@ -100,13 +97,13 @@ where D: Deserializer<'de>, { let value = String::deserialize(d)?; - if value.len() >= 2 && &value[0..2] == "0x" { - let bytes: Vec = - hex::decode(&value[2..]).map_err(|e| Error::custom(format!("Invalid hex: {}", e)))?; - Ok(bytes.into()) + if let Some(value) = value.strip_prefix("0x") { + hex::decode(value) } else { - Err(Error::invalid_value(Unexpected::Str(&value), &"0x prefix")) + hex::decode(&value) } + .map(Into::into) + .map_err(|e| serde::de::Error::custom(e.to_string())) } #[cfg(test)] @@ -129,7 +126,7 @@ mod tests { assert_eq!(b.as_ref(), hex::decode("1213").unwrap()); let b = Bytes::from_str("1213"); - assert!(b.is_err()); - assert_eq!(b.err().unwrap().0, "Doesn't start with 0x"); + let b = b.unwrap(); + assert_eq!(b.as_ref(), hex::decode("1213").unwrap()); } } diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs index 5083fbb4..4c3b8a12 100644 --- a/ethers-core/src/types/mod.rs +++ b/ethers-core/src/types/mod.rs @@ -25,7 +25,7 @@ mod i256; pub use i256::{Sign, I256}; mod bytes; -pub use self::bytes::{Bytes, ParseBytesError}; +pub use self::bytes::{deserialize_bytes, serialize_bytes, Bytes, ParseBytesError}; mod block; pub use block::{Block, BlockId, BlockNumber}; diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs index d2a6c151..b44a1384 100644 --- a/ethers-solc/src/artifacts/serde_helpers.rs +++ b/ethers-solc/src/artifacts/serde_helpers.rs @@ -7,14 +7,7 @@ pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result where D: Deserializer<'de>, { - let value = String::deserialize(d)?; - if let Some(value) = value.strip_prefix("0x") { - hex::decode(value) - } else { - hex::decode(&value) - } - .map(Into::into) - .map_err(|e| serde::de::Error::custom(e.to_string())) + String::deserialize(d)?.parse::().map_err(|e| serde::de::Error::custom(e.to_string())) } pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result, D::Error> diff --git a/examples/contract_with_abi.rs b/examples/contract_with_abi.rs index c151649e..743b3504 100644 --- a/examples/contract_with_abi.rs +++ b/examples/contract_with_abi.rs @@ -3,7 +3,7 @@ use eyre::Result; use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration}; // Generate the type-safe contract bindings by providing the ABI -// definition in human readable format +// definition abigen!( SimpleContract, "./examples/contract_abi.json", diff --git a/examples/contract_with_abi_and_bytecode.rs b/examples/contract_with_abi_and_bytecode.rs new file mode 100644 index 00000000..deda6c41 --- /dev/null +++ b/examples/contract_with_abi_and_bytecode.rs @@ -0,0 +1,36 @@ +use ethers::{prelude::*, utils::Ganache}; +use eyre::Result; +use std::{convert::TryFrom, sync::Arc, time::Duration}; + +// Generate the type-safe contract bindings by providing the json artifact +// *Note*: this requires a `bytecode` and `abi` object in the `greeter.json` artifact: +// `{"abi": [..], "bin": "..."}` or `{"abi": [..], "bytecode": {"object": "..."}}` +// this will embedd the bytecode in a variable `GREETER_BYTECODE` +abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter.json",); + +#[tokio::main] +async fn main() -> Result<()> { + // 1. compile the contract (note this requires that you are inside the `examples` directory) and + // launch ganache + let ganache = Ganache::new().spawn(); + + // 2. instantiate our wallet + let wallet: LocalWallet = ganache.keys()[0].clone().into(); + + // 3. connect to the network + let provider = + Provider::::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64)); + + // 4. instantiate the client with the wallet + let client = Arc::new(SignerMiddleware::new(provider, wallet)); + + // 5. deploy contract, note the `legacy` call required for non EIP-1559 + let greeter_contract = + Greeter::deploy(client, "Hello World!".to_string()).unwrap().legacy().send().await.unwrap(); + + // 6. call contract function + let greeting = greeter_contract.greet().call().await.unwrap(); + assert_eq!("Hello World!", greeting); + + Ok(()) +}