Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
feat(abigen): add abi object deserializer and generate deploy function (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
mattsse authored Mar 19, 2022
1 parent 2621499 commit b6b5b09
Show file tree
Hide file tree
Showing 14 changed files with 469 additions and 63 deletions.
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

0 comments on commit b6b5b09

Please sign in to comment.