Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(abigen): add abi object deserializer and generate deploy function #1030

Merged
merged 19 commits into from
Mar 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
97 changes: 67 additions & 30 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ 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},
};
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;
Expand Down Expand Up @@ -89,6 +92,9 @@ pub struct Context {

/// Manually specified event aliases.
event_aliases: BTreeMap<String, Ident>,

/// Bytecode extracted from the abi string input, if present.
contract_bytecode: Option<Bytes>,
}

impl Context {
Expand All @@ -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()?;
Expand All @@ -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();
Expand All @@ -130,16 +138,21 @@ impl Context {
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object
pub fn new<T: Into<#ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> 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<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
fn from(contract: #ethers_contract::Contract<M>) -> Self {
Self(contract)
}
}
};

Ok(ExpandedContract {
Expand All @@ -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
Expand All @@ -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::<RawAbi>(&abi_str) {
if let Ok(s) = serde_json::to_string(&abi) {
} else {
match serde_json::from_str::<JsonAbi>(&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::<RawAbi>(&abi_str)
.ok()
.map(InternalStructs::new)
.unwrap_or_default()
};

let contract_ident = util::ident(&args.contract_name);
Expand Down Expand Up @@ -231,6 +242,7 @@ impl Context {
internal_structs,
contract_ident,
contract_name: args.contract_name,
contract_bytecode,
method_aliases,
event_derives,
event_aliases,
Expand All @@ -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
Expand All @@ -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::<Contract>(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,
}
}
}
18 changes: 17 additions & 1 deletion ethers-contract/ethers-contract-abigen/src/contract/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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<M>(#ethers_contract::Contract<M>);
Expand Down
55 changes: 55 additions & 0 deletions ethers-contract/ethers-contract-abigen/src/contract/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<M: ethers::providers::Middleware>(client: ::std::sync::Arc<M>) {
/// 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<T: #ethers_core::abi::Tokenize >(client: ::std::sync::Arc<M>, constructor_args: T) -> Result<#ethers_contract::builders::ContractDeployer<M, Self>, #ethers_contract::ContractError<M>> {
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,
Expand Down
Loading