From ea29cdaa5e77ec60e1713fcfe063bf6d816e060d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 15 Mar 2022 00:15:04 +0100 Subject: [PATCH 01/15] feat(abigen): add abi object deserializer --- .../ethers-contract-abigen/src/contract.rs | 2 + .../ethers-contract-abigen/src/rawabi.rs | 115 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index c9652f3dd..31db5f141 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -14,6 +14,7 @@ use ethers_core::{ use eyre::{eyre, Context as _, Result}; use crate::contract::methods::MethodAlias; + use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; use serde::Deserialize; @@ -187,6 +188,7 @@ impl Context { InternalStructs::default() } } else { + // parse as serde_json::from_str::(&abi_str) .ok() .map(InternalStructs::new) diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index 782e4d35d..5544cbe21 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,6 +106,93 @@ 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 = Some(map.next_value::()?.object); + } + "bin" => { + bytecode = Some(map.next_value::()?.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::*; @@ -140,4 +228,31 @@ 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); + match serde_json::from_str::(&s).unwrap() { + JsonAbi::Object(abi) => { + assert!(abi.bytecode.is_some()); + } + _ => { + panic!("expected abi object") + } + } + let s = format!(r#"{{"abi": {}, "bytecode" : {{ "object": "{}" }} }}"#, abi_str, code); + match serde_json::from_str::(&s).unwrap() { + JsonAbi::Object(abi) => { + assert!(abi.bytecode.is_some()); + } + _ => { + panic!("expected abi object") + } + } + } } From 2813cff9d3a5d0e28db6c9ff9d8547e358d44721 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 15 Mar 2022 09:07:48 +0100 Subject: [PATCH 02/15] chore: rustfmt --- ethers-core/src/types/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs index 6e821163b..4a4328388 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; +pub use self::bytes::{deserialize_bytes, serialize_bytes, Bytes}; mod block; pub use block::{Block, BlockId, BlockNumber}; From 853ddb784e67b392f6150ca4e09c380f248dc24d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 16 Mar 2022 23:46:13 +0100 Subject: [PATCH 03/15] refactor: use enum type for deser abi --- .../ethers-contract-abigen/src/contract.rs | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 31db5f141..31e259093 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -261,18 +261,36 @@ 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} + } + } + +} \ No newline at end of file From 23e36a533969db6d9802626f7552d66885a8df3d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 00:23:00 +0100 Subject: [PATCH 04/15] refactor: use enum types for deser --- .../ethers-contract-abigen/src/contract.rs | 28 ++++++++----------- .../ethers-contract-abigen/src/rawabi.rs | 14 ++++++++-- ethers-contract/tests/abigen.rs | 2 +- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 31e259093..97b16338e 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}, @@ -20,6 +20,7 @@ use quote::quote; use serde::Deserialize; use std::collections::BTreeMap; use syn::Path; +use crate::rawabi::JsonAbi; /// The result of `Context::expand` #[derive(Debug)] @@ -176,23 +177,18 @@ 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) { - // 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; + } 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 = serde_json::to_string(&obj.abi)?; + InternalStructs::new(obj.abi) + } + JsonAbi::Array(abi) => { + InternalStructs::new(abi) } - InternalStructs::new(abi) - } else { - InternalStructs::default() } - } else { - // parse as - serde_json::from_str::(&abi_str) - .ok() - .map(InternalStructs::new) - .unwrap_or_default() }; let contract_ident = util::ident(&args.contract_name); diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index 5544cbe21..5d8fd64a5 100644 --- a/ethers-contract/ethers-contract-abigen/src/rawabi.rs +++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs @@ -168,10 +168,10 @@ impl<'de> Visitor<'de> for AbiObjectVisitor { abi = Some(RawAbi(map.next_value::>()?)); } "bytecode" | "byteCode" => { - bytecode = Some(map.next_value::()?.object); + bytecode = map.next_value::().ok().map(|obj|obj.object); } "bin" => { - bytecode = Some(map.next_value::()?.0); + bytecode = map.next_value::().ok().map(|b|b.0); } _ => { map.next_value::()?; @@ -254,5 +254,15 @@ mod tests { panic!("expected abi object") } } + + 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") + } + } } } diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 80f5616c1..9017633da 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -382,7 +382,7 @@ fn can_handle_underscore_numeric() { _100pct(string) ]"# ); - let call = _100PctCall("message".to_string()); + let _call = _100PctCall("message".to_string()); let provider = Arc::new(Provider::new(MockProvider::new())); let contract = Test::new(Address::default(), Arc::clone(&provider)); From b56e8f97475941b21e9f2d863de035d15561d3fb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 00:24:23 +0100 Subject: [PATCH 05/15] chore: rustfmt --- .../ethers-contract-abigen/src/contract.rs | 23 ++++++++----------- .../ethers-contract-abigen/src/rawabi.rs | 4 ++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 97b16338e..710a10af1 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}; +use crate::contract::structs::InternalStructs; use ethers_core::{ abi::{Abi, AbiParser}, macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}, @@ -15,12 +15,12 @@ use eyre::{eyre, Context as _, Result}; use crate::contract::methods::MethodAlias; +use crate::rawabi::JsonAbi; use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; use serde::Deserialize; use std::collections::BTreeMap; use syn::Path; -use crate::rawabi::JsonAbi; /// The result of `Context::expand` #[derive(Debug)] @@ -180,14 +180,12 @@ impl Context { } 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 + // need to update the `abi_str` here because we only want the `"abi": [...]` + // part of the json object in the contract binding abi_str = serde_json::to_string(&obj.abi)?; InternalStructs::new(obj.abi) } - JsonAbi::Array(abi) => { - InternalStructs::new(abi) - } + JsonAbi::Array(abi) => InternalStructs::new(abi), } }; @@ -257,9 +255,8 @@ 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 { - // a best-effort coercion of an ABI or an artifact JSON into an artifact JSON. - let contract: JsonContract = serde_json::from_str(abi_str)?; + let contract: JsonContract = serde_json::from_str(abi_str)?; (contract.into_abi(), false, abi_parser) }; @@ -281,12 +278,10 @@ enum JsonContract { } impl JsonContract { - fn into_abi(self) -> Abi { match self { - JsonContract::Object(o) => {o.abi} - JsonContract::Array(abi) => {abi} + JsonContract::Object(o) => o.abi, + JsonContract::Array(abi) => abi, } } - -} \ No newline at end of file +} diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index 5d8fd64a5..d266eeca9 100644 --- a/ethers-contract/ethers-contract-abigen/src/rawabi.rs +++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs @@ -168,10 +168,10 @@ impl<'de> Visitor<'de> for AbiObjectVisitor { abi = Some(RawAbi(map.next_value::>()?)); } "bytecode" | "byteCode" => { - bytecode = map.next_value::().ok().map(|obj|obj.object); + bytecode = map.next_value::().ok().map(|obj| obj.object); } "bin" => { - bytecode = map.next_value::().ok().map(|b|b.0); + bytecode = map.next_value::().ok().map(|b| b.0); } _ => { map.next_value::()?; From d5991e0cdc21d4611521c6234ddd618e097c2abb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 00:32:00 +0100 Subject: [PATCH 06/15] feat: add bytecode field --- ethers-contract/ethers-contract-abigen/src/contract.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 710a10af1..b8c71ab02 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -21,6 +21,7 @@ use quote::quote; use serde::Deserialize; use std::collections::BTreeMap; use syn::Path; +use ethers_core::types::Bytes; /// The result of `Context::expand` #[derive(Debug)] @@ -91,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 { @@ -160,6 +164,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 @@ -183,6 +190,7 @@ impl Context { // need to update the `abi_str` here because we only want the `"abi": [...]` // part of the json object in the contract binding abi_str = serde_json::to_string(&obj.abi)?; + contract_bytecode = obj.bytecode; InternalStructs::new(obj.abi) } JsonAbi::Array(abi) => InternalStructs::new(abi), @@ -227,6 +235,7 @@ impl Context { internal_structs, contract_ident, contract_name: args.contract_name, + contract_bytecode, method_aliases, event_derives, event_aliases, From 7be549bf16a36fd5edfd62c2edc5a082f88bd064 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 00:51:13 +0100 Subject: [PATCH 07/15] feat: generate bytecode static --- .../ethers-contract-abigen/src/contract.rs | 15 ++++++++++++--- .../src/contract/common.rs | 18 +++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index b8c71ab02..515f0965c 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -103,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()?; @@ -247,6 +246,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 diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs index 07a008e7c..818c4ab9e 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); From 8b50237d3cf31d93f1ddf8c2aa46a81876306884 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 09:47:07 +0100 Subject: [PATCH 08/15] feat: generate deployment function --- .../ethers-contract-abigen/src/contract.rs | 9 ++-- .../src/contract/methods.rs | 54 +++++++++++++++++++ ethers-contract/src/factory.rs | 5 +- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 515f0965c..bc4c93a33 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -16,12 +16,12 @@ 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; use std::collections::BTreeMap; use syn::Path; -use ethers_core::types::Bytes; /// The result of `Context::expand` #[derive(Debug)] @@ -120,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(); @@ -139,7 +142,7 @@ impl Context { Self(contract) } - // TODO: Implement deployment. + #deployment_methods #contract_methods diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 4d54aea27..7cc2a9c3f 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -42,6 +42,60 @@ 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 get_client = quote! { + self.deref().client().clone() + }; + let deploy_tokens = quote! { + /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments + /// You must call `send()` in order to actually deploy the contract. + /// + /// 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. + pub fn deploy_tokens(self, constructor_args: T) -> Result<#ethers_contract::factory::Deployer, #ethers_contract::call::ContractError> { + let factory = #ethers_contract::factory::ContractFactory::new(#get_abi, #get_bytecode, #get_client); + factory.deploy_tokens(constructor_args.into_tokens()) + } + + }; + // /// Constructs the deployment transaction and returns a `Deployer` instance. + // /// You must call `send()` in order to actually deploy the contract. + // /// + // /// 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. + // pub fn deploy(self, constructor_args: T) -> Result, + // ContractError> { self.deploy_tokens(constructor_args. + // into_tokens()) // let factory = ContractFactory::new(abi.unwrap(), + // bytecode.unwrap(), client.clone()); } + + return quote! { + + #deploy_tokens + } + } + + 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/src/factory.rs b/ethers-contract/src/factory.rs index 9e7103ca7..3715e547f 100644 --- a/ethers-contract/src/factory.rs +++ b/ethers-contract/src/factory.rs @@ -14,8 +14,9 @@ use ethers_core::types::Eip1559TransactionRequest; use std::sync::Arc; -#[derive(Debug, Clone)] /// 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, @@ -109,7 +110,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 +151,7 @@ impl Deployer { /// println!("{}", contract.address()); /// # Ok(()) /// # } +#[derive(Debug, Clone)] pub struct ContractFactory { client: Arc, abi: Abi, From 1e04fa027a49dcc2ddcb0e4d69d2de800b66db56 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 10:01:54 +0100 Subject: [PATCH 09/15] refactor: deploy function --- .../src/contract/methods.rs | 37 ++++++------------- ethers-contract/tests/abigen.rs | 21 +++++++++++ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 7cc2a9c3f..c4ed893ca 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -58,39 +58,26 @@ impl Context { #bytecode_name.clone().into() }; - let get_client = quote! { - self.deref().client().clone() - }; - let deploy_tokens = quote! { - /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments - /// You must call `send()` in order to actually deploy the contract. + let deploy = quote! { + /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it. + /// Returns a new instance of this contract afterwards /// /// 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. - pub fn deploy_tokens(self, constructor_args: T) -> Result<#ethers_contract::factory::Deployer, #ethers_contract::call::ContractError> { - let factory = #ethers_contract::factory::ContractFactory::new(#get_abi, #get_bytecode, #get_client); - factory.deploy_tokens(constructor_args.into_tokens()) + // TODO make this return an additional helper type + pub async fn deploy(client client: ::std::sync::Arc, constructor_args: T) -> Result<#ethers_contract::factory::Deployer, #ethers_contract::call::ContractError> { + let factory = #ethers_contract::factory::ContractFactory::new(#get_abi, #get_bytecode, :client); + let contract = factory.deploy(constructor_args)?.send().await?; + Self(contract) } }; - // /// Constructs the deployment transaction and returns a `Deployer` instance. - // /// You must call `send()` in order to actually deploy the contract. - // /// - // /// 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. - // pub fn deploy(self, constructor_args: T) -> Result, - // ContractError> { self.deploy_tokens(constructor_args. - // into_tokens()) // let factory = ContractFactory::new(abi.unwrap(), - // bytecode.unwrap(), client.clone()); } - - return quote! { - - #deploy_tokens - } + + // TODO generate all constructors, + + return deploy } quote! {} diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 9017633da..e1adb8889 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -478,6 +478,27 @@ fn can_handle_case_sensitive_calls() { let _ = contract.INDEX(); } +#[tokio::test] +async fn can_deploy_greeter() { + abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter_with_struct.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); + + Greeter::deploy(client, ()); + // let addr = factory.deploy(()).unwrap().legacy().send().await.unwrap().address(); + // + // let contract = AbiEncoderv2Test::new(addr, client.clone()); + // let person = Person { name: "Alice".to_string(), age: 20u64.into() }; + // + // let res = contract.default_person().call().await.unwrap(); + // assert_eq!(res, person); +} + #[tokio::test] async fn can_abiencoderv2_output() { abigen!(AbiEncoderv2Test, "ethers-contract/tests/solidity-contracts/abiencoderv2test_abi.json",); From 04b09a66d1256bbe69a277fb9d60b305fadd9459 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 18 Mar 2022 13:12:03 +0100 Subject: [PATCH 10/15] feat: add contract deployer type --- .../ethers-contract-abigen/src/contract.rs | 10 ++- .../src/contract/methods.rs | 28 +++++-- ethers-contract/src/factory.rs | 77 +++++++++++++++++++ ethers-contract/src/lib.rs | 6 +- 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index bc4c93a33..c5e86f00f 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -133,13 +133,13 @@ impl Context { let contract = quote! { #struct_decl + impl<'a, M: #ethers_providers::Middleware> #name { /// Creates a new contract instance with the specified `ethers` /// 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() } #deployment_methods @@ -148,6 +148,12 @@ impl Context { #contract_events } + + impl From<#ethers_contract::Contract> for #name { + fn from(contract: #ethers_contract::Contract) -> Self { + Self(contract) + } + } }; Ok(ExpandedContract { diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index c4ed893ca..ca4781f0b 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -60,23 +60,37 @@ impl Context { let deploy = quote! { /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it. - /// Returns a new instance of this contract afterwards + /// 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. - // TODO make this return an additional helper type - pub async fn deploy(client client: ::std::sync::Arc, constructor_args: T) -> Result<#ethers_contract::factory::Deployer, #ethers_contract::call::ContractError> { + /// + /// + /// # 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, ()).unwrap().send().await.unwrap(); + /// let msg = greeter_contract.greet().await.unwrap(); + /// } + /// ``` + pub async fn deploy(client client: ::std::sync::Arc, constructor_args: T) -> Result<#ethers_contract::builders::ContractDeployer, #ethers_contract::call::ContractError> { let factory = #ethers_contract::factory::ContractFactory::new(#get_abi, #get_bytecode, :client); - let contract = factory.deploy(constructor_args)?.send().await?; - Self(contract) + let deployer = factory.deploy(constructor_args)?; + let deployer = #ethers_contract::builders::ContractDeployer::new(deployer); + Ok(deployer) } }; - // TODO generate all constructors, - return deploy } diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs index 3715e547f..6e4de6475 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,6 +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 + #[must_use] + pub fn confirmations>(mut self, confirmations: T) -> Self { + self.deployer.confs = confirmations.into(); + self + } + + #[must_use] + 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 + #[must_use] + 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"] diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 98074b00a..93ecdef95 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -26,7 +26,11 @@ pub use multicall::Multicall; /// This module exposes low lever builder structures which are only consumed by the /// type-safe ABI bindings generators. 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"))] From c4bee2909e46fca4f65dadf09d88744f8ad385d7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 18 Mar 2022 13:46:07 +0100 Subject: [PATCH 11/15] feat: make 0x prefix optional --- ethers-core/src/types/bytes.rs | 25 +++++++++++----------- ethers-solc/src/artifacts/serde_helpers.rs | 9 +------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/ethers-core/src/types/bytes.rs b/ethers-core/src/types/bytes.rs index 21f2c719a..8672c652a 100644 --- a/ethers-core/src/types/bytes.rs +++ b/ethers-core/src/types/bytes.rs @@ -1,5 +1,4 @@ use serde::{ - de::{Error, Unexpected}, Deserialize, Deserializer, Serialize, Serializer, }; use thiserror::Error; @@ -77,13 +76,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 +99,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 +128,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-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs index d2a6c1516..b44a1384e 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> From 8edf096e642516ccea4158a821c2e5c718ba4e4f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 18 Mar 2022 14:13:17 +0100 Subject: [PATCH 12/15] feat: add deploy function --- .../ethers-contract-abigen/src/contract.rs | 1 - .../src/contract/methods.rs | 12 ++--- .../ethers-contract-abigen/src/rawabi.rs | 36 ++++++++------- ethers-contract/src/lib.rs | 3 +- ethers-contract/tests/abigen.rs | 15 +++--- .../tests/solidity-contracts/greeter.json | 46 +++++++++++++++++++ ethers-core/src/types/bytes.rs | 4 +- 7 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 ethers-contract/tests/solidity-contracts/greeter.json diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index c5e86f00f..2dd4a4fae 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -133,7 +133,6 @@ impl Context { let contract = quote! { #struct_decl - impl<'a, M: #ethers_providers::Middleware> #name { /// Creates a new contract instance with the specified `ethers` /// client at the given `Address`. The contract derefs to a `ethers::Contract` diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index ca4781f0b..a9ca643a1 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -78,14 +78,14 @@ impl Context { /// # async fn deploy(client: ::std::sync::Arc) { /// abigen!(Greeter,"../greeter.json"); /// - /// let greeter_contract = Greeter::deploy(client, ()).unwrap().send().await.unwrap(); - /// let msg = greeter_contract.greet().await.unwrap(); - /// } + /// let greeter_contract = Greeter::deploy(client, "Hello world!".to_string()).unwrap().send().await.unwrap(); + /// let msg = greeter_contract.greet().call().await.unwrap(); + /// # } /// ``` - pub async fn deploy(client client: ::std::sync::Arc, constructor_args: T) -> Result<#ethers_contract::builders::ContractDeployer, #ethers_contract::call::ContractError> { - let factory = #ethers_contract::factory::ContractFactory::new(#get_abi, #get_bytecode, :client); + 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::builders::ContractDeployer::new(deployer); + let deployer = #ethers_contract::ContractDeployer::new(deployer); Ok(deployer) } diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index d266eeca9..501ecc8a7 100644 --- a/ethers-contract/ethers-contract-abigen/src/rawabi.rs +++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs @@ -198,6 +198,17 @@ 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"); @@ -237,23 +248,10 @@ mod tests { let code = "0x608060405234801561001057600080fd5b50610242806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80635581701b14610030575b600080fd5b61004a60048036038101906100459190610199565b610060565b60405161005791906101f1565b60405180910390f35b610068610070565b819050919050565b60405180602001604052806000151581525090565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100e282610099565b810181811067ffffffffffffffff82111715610101576101006100aa565b5b80604052505050565b6000610114610085565b905061012082826100d9565b919050565b60008115159050919050565b61013a81610125565b811461014557600080fd5b50565b60008135905061015781610131565b92915050565b60006020828403121561017357610172610094565b5b61017d602061010a565b9050600061018d84828501610148565b60008301525092915050565b6000602082840312156101af576101ae61008f565b5b60006101bd8482850161015d565b91505092915050565b6101cf81610125565b82525050565b6020820160008201516101eb60008501826101c6565b50505050565b600060208201905061020660008301846101d5565b9291505056fea2646970667358221220890202b0964477379a457ab3725a21d7c14581e4596552e32a54e23f1c6564e064736f6c634300080c0033"; let s = format!(r#"{{"abi": {}, "bin" : "{}" }}"#, abi_str, code); - match serde_json::from_str::(&s).unwrap() { - JsonAbi::Object(abi) => { - assert!(abi.bytecode.is_some()); - } - _ => { - panic!("expected abi object") - } - } + assert_has_bytecode(&s); + let s = format!(r#"{{"abi": {}, "bytecode" : {{ "object": "{}" }} }}"#, abi_str, code); - match serde_json::from_str::(&s).unwrap() { - JsonAbi::Object(abi) => { - assert!(abi.bytecode.is_some()); - } - _ => { - panic!("expected abi object") - } - } + assert_has_bytecode(&s); let hh_artifact = include_str!("../../tests/solidity-contracts/verifier_abi_hardhat.json"); match serde_json::from_str::(hh_artifact).unwrap() { @@ -265,4 +263,10 @@ mod tests { } } } + + #[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/lib.rs b/ethers-contract/src/lib.rs index 93ecdef95..1d1532ac3 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,6 +25,7 @@ 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, diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index e1adb8889..63faf1339 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -480,7 +480,7 @@ fn can_handle_case_sensitive_calls() { #[tokio::test] async fn can_deploy_greeter() { - abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter_with_struct.json",); + 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()) @@ -489,14 +489,11 @@ async fn can_deploy_greeter() { .interval(std::time::Duration::from_millis(10)); let client = Arc::new(provider); - Greeter::deploy(client, ()); - // let addr = factory.deploy(()).unwrap().legacy().send().await.unwrap().address(); - // - // let contract = AbiEncoderv2Test::new(addr, client.clone()); - // let person = Person { name: "Alice".to_string(), age: 20u64.into() }; - // - // let res = contract.default_person().call().await.unwrap(); - // assert_eq!(res, person); + 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] diff --git a/ethers-contract/tests/solidity-contracts/greeter.json b/ethers-contract/tests/solidity-contracts/greeter.json new file mode 100644 index 000000000..1abacd125 --- /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 8672c652a..d8728362a 100644 --- a/ethers-core/src/types/bytes.rs +++ b/ethers-core/src/types/bytes.rs @@ -1,6 +1,4 @@ -use serde::{ - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; use std::{ From 94433216ffcbec07c9ec9c231d696065bd4cc29b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 18 Mar 2022 14:22:57 +0100 Subject: [PATCH 13/15] feat: add deploy example --- examples/contract_with_abi.rs | 2 +- examples/contract_with_abi_and_bytecode.rs | 36 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 examples/contract_with_abi_and_bytecode.rs diff --git a/examples/contract_with_abi.rs b/examples/contract_with_abi.rs index c151649ee..743b3504b 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 000000000..deda6c41e --- /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(()) +} From 765d98277f773c55d9b375fb1056d8f334150ee5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 18 Mar 2022 14:24:54 +0100 Subject: [PATCH 14/15] chore: update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b34a53d8d..12f6b1d9e 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). From 38ebbb9aaa36490fc81129b1e08a53dbe45d64c9 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 18 Mar 2022 14:30:31 +0100 Subject: [PATCH 15/15] chore(clippy): make clippy happy --- ethers-contract/src/factory.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs index 6e4de6475..0df95da1f 100644 --- a/ethers-contract/src/factory.rs +++ b/ethers-contract/src/factory.rs @@ -37,20 +37,17 @@ impl>> ContractDeployer { } /// Sets the number of confirmations to wait for the contract deployment transaction - #[must_use] pub fn confirmations>(mut self, confirmations: T) -> Self { self.deployer.confs = confirmations.into(); self } - #[must_use] 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 - #[must_use] pub fn legacy(mut self) -> Self { self.deployer = self.deployer.legacy(); self @@ -105,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) => {