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
This commit is contained in:
parent
262149945a
commit
b6b5b09f4a
|
@ -55,6 +55,8 @@
|
||||||
|
|
||||||
### Unreleased
|
### 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
|
- Generate correct bindings of struct's field names that are reserved words
|
||||||
[#989](https://github.com/gakonst/ethers-rs/pull/989).
|
[#989](https://github.com/gakonst/ethers-rs/pull/989).
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod structs;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
use super::{util, Abigen};
|
use super::{util, Abigen};
|
||||||
use crate::{contract::structs::InternalStructs, rawabi::RawAbi};
|
use crate::contract::structs::InternalStructs;
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Abi, AbiParser},
|
abi::{Abi, AbiParser},
|
||||||
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
|
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
|
||||||
|
@ -14,6 +14,9 @@ use ethers_core::{
|
||||||
use eyre::{eyre, Context as _, Result};
|
use eyre::{eyre, Context as _, Result};
|
||||||
|
|
||||||
use crate::contract::methods::MethodAlias;
|
use crate::contract::methods::MethodAlias;
|
||||||
|
|
||||||
|
use crate::rawabi::JsonAbi;
|
||||||
|
use ethers_core::types::Bytes;
|
||||||
use proc_macro2::{Ident, Literal, TokenStream};
|
use proc_macro2::{Ident, Literal, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -89,6 +92,9 @@ pub struct Context {
|
||||||
|
|
||||||
/// Manually specified event aliases.
|
/// Manually specified event aliases.
|
||||||
event_aliases: BTreeMap<String, Ident>,
|
event_aliases: BTreeMap<String, Ident>,
|
||||||
|
|
||||||
|
/// Bytecode extracted from the abi string input, if present.
|
||||||
|
contract_bytecode: Option<Bytes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
@ -97,14 +103,13 @@ impl Context {
|
||||||
let name = &self.contract_ident;
|
let name = &self.contract_ident;
|
||||||
let name_mod =
|
let name_mod =
|
||||||
util::ident(&format!("{}_mod", self.contract_ident.to_string().to_lowercase()));
|
util::ident(&format!("{}_mod", self.contract_ident.to_string().to_lowercase()));
|
||||||
|
let abi_name = self.inline_abi_ident();
|
||||||
let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase()));
|
|
||||||
|
|
||||||
// 0. Imports
|
// 0. Imports
|
||||||
let imports = common::imports(&name.to_string());
|
let imports = common::imports(&name.to_string());
|
||||||
|
|
||||||
// 1. Declare Contract struct
|
// 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
|
// 2. Declare events structs & impl FromTokens for each event
|
||||||
let events_decl = self.events_declaration()?;
|
let events_decl = self.events_declaration()?;
|
||||||
|
@ -115,7 +120,10 @@ impl Context {
|
||||||
// 4. impl block for the contract methods and their corresponding types
|
// 4. impl block for the contract methods and their corresponding types
|
||||||
let (contract_methods, call_structs) = self.methods_and_call_structs()?;
|
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 abi_structs_decl = self.abi_structs()?;
|
||||||
|
|
||||||
let ethers_core = ethers_core_crate();
|
let ethers_core = ethers_core_crate();
|
||||||
|
@ -130,16 +138,21 @@ impl Context {
|
||||||
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
||||||
/// object
|
/// object
|
||||||
pub fn new<T: Into<#ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self {
|
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);
|
#ethers_contract::Contract::new(address.into(), #abi_name.clone(), client).into()
|
||||||
Self(contract)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement deployment.
|
#deployment_methods
|
||||||
|
|
||||||
#contract_methods
|
#contract_methods
|
||||||
|
|
||||||
#contract_events
|
#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 {
|
Ok(ExpandedContract {
|
||||||
|
@ -158,6 +171,9 @@ impl Context {
|
||||||
let mut abi_str =
|
let mut abi_str =
|
||||||
args.abi_source.get().map_err(|e| eyre!("failed to get ABI JSON: {}", e))?;
|
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)?;
|
let (abi, human_readable, abi_parser) = parse_abi(&abi_str)?;
|
||||||
|
|
||||||
// try to extract all the solidity structs from the normal JSON ABI
|
// 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.outputs = abi_parser.outputs.clone();
|
||||||
|
|
||||||
internal_structs
|
internal_structs
|
||||||
} else if abi_str.starts_with('{') {
|
} else {
|
||||||
if let Ok(abi) = serde_json::from_str::<RawAbi>(&abi_str) {
|
match serde_json::from_str::<JsonAbi>(&abi_str)? {
|
||||||
if let Ok(s) = serde_json::to_string(&abi) {
|
JsonAbi::Object(obj) => {
|
||||||
// need to update the `abi_str` here because we only want the `"abi": [...]`
|
// need to update the `abi_str` here because we only want the `"abi": [...]`
|
||||||
// part of the json object in the contract binding
|
// 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)
|
JsonAbi::Array(abi) => InternalStructs::new(abi),
|
||||||
} else {
|
|
||||||
InternalStructs::default()
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
serde_json::from_str::<RawAbi>(&abi_str)
|
|
||||||
.ok()
|
|
||||||
.map(InternalStructs::new)
|
|
||||||
.unwrap_or_default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let contract_ident = util::ident(&args.contract_name);
|
let contract_ident = util::ident(&args.contract_name);
|
||||||
|
@ -231,6 +242,7 @@ impl Context {
|
||||||
internal_structs,
|
internal_structs,
|
||||||
contract_ident,
|
contract_ident,
|
||||||
contract_name: args.contract_name,
|
contract_name: args.contract_name,
|
||||||
|
contract_bytecode,
|
||||||
method_aliases,
|
method_aliases,
|
||||||
event_derives,
|
event_derives,
|
||||||
event_aliases,
|
event_aliases,
|
||||||
|
@ -242,6 +254,16 @@ impl Context {
|
||||||
&self.contract_name
|
&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
|
/// The internal abi struct mapping table
|
||||||
pub fn internal_structs(&self) -> &InternalStructs {
|
pub fn internal_structs(&self) -> &InternalStructs {
|
||||||
&self.internal_structs
|
&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) {
|
let res = if let Ok(abi) = abi_parser.parse_str(abi_str) {
|
||||||
(abi, true, abi_parser)
|
(abi, true, abi_parser)
|
||||||
} else {
|
} else {
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Contract {
|
|
||||||
abi: Abi,
|
|
||||||
}
|
|
||||||
// a best-effort coercion of an ABI or an artifact JSON into an artifact JSON.
|
// 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('[') {
|
let contract: JsonContract = serde_json::from_str(abi_str)?;
|
||||||
serde_json::from_str(&format!(r#"{{"abi":{}}}"#, abi_str.trim()))?
|
|
||||||
} else {
|
|
||||||
serde_json::from_str::<Contract>(abi_str)?
|
|
||||||
};
|
|
||||||
|
|
||||||
(contract.abi, false, abi_parser)
|
(contract.into_abi(), false, abi_parser)
|
||||||
};
|
};
|
||||||
Ok(res)
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,10 +30,12 @@ pub(crate) fn imports(name: &str) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the static `Abi` constants and the contract struct
|
/// 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 name = &cx.contract_ident;
|
||||||
let abi = &cx.abi_str;
|
let abi = &cx.abi_str;
|
||||||
|
|
||||||
|
let abi_name = cx.inline_abi_ident();
|
||||||
|
|
||||||
let ethers_core = ethers_core_crate();
|
let ethers_core = ethers_core_crate();
|
||||||
let ethers_providers = ethers_providers_crate();
|
let ethers_providers = ethers_providers_crate();
|
||||||
let ethers_contract = ethers_contract_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! {
|
quote! {
|
||||||
// Inline ABI declaration
|
// Inline ABI declaration
|
||||||
#abi_parse
|
#abi_parse
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
// Struct declaration
|
// Struct declaration
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct #name<M>(#ethers_contract::Contract<M>);
|
pub struct #name<M>(#ethers_contract::Contract<M>);
|
||||||
|
|
|
@ -42,6 +42,61 @@ impl Context {
|
||||||
Ok((function_impls, call_structs))
|
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
|
/// Expands to the corresponding struct type based on the inputs of the given function
|
||||||
fn expand_call_struct(
|
fn expand_call_struct(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//! raw content of the ABI.
|
//! raw content of the ABI.
|
||||||
|
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
use ethers_core::types::Bytes;
|
||||||
use serde::{
|
use serde::{
|
||||||
de::{MapAccess, SeqAccess, Visitor},
|
de::{MapAccess, SeqAccess, Visitor},
|
||||||
Deserialize, Deserializer, Serialize,
|
Deserialize, Deserializer, Serialize,
|
||||||
|
@ -105,11 +106,109 @@ pub struct Component {
|
||||||
pub indexed: Option<bool>,
|
pub indexed: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<RawAbi, D::Error>
|
||||||
|
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<Bytes>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||||
|
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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(DeserializeBytes(ethers_core::types::deserialize_bytes(deserializer)?.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key::<String>()? {
|
||||||
|
match key.as_str() {
|
||||||
|
"abi" => {
|
||||||
|
abi = Some(RawAbi(map.next_value::<Vec<Item>>()?));
|
||||||
|
}
|
||||||
|
"bytecode" | "byteCode" => {
|
||||||
|
bytecode = map.next_value::<BytecodeObject>().ok().map(|obj| obj.object);
|
||||||
|
}
|
||||||
|
"bin" => {
|
||||||
|
bytecode = map.next_value::<DeserializeBytes>().ok().map(|b| b.0);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
map.next_value::<serde::de::IgnoredAny>()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let abi = abi.ok_or_else(|| serde::de::Error::missing_field("abi"))?;
|
||||||
|
Ok(AbiObject { abi, bytecode })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for AbiObject {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_map(AbiObjectVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ethers_core::abi::Abi;
|
use ethers_core::abi::Abi;
|
||||||
|
|
||||||
|
fn assert_has_bytecode(s: &str) {
|
||||||
|
match serde_json::from_str::<JsonAbi>(&s).unwrap() {
|
||||||
|
JsonAbi::Object(abi) => {
|
||||||
|
assert!(abi.bytecode.is_some());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("expected abi object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_parse_raw_abi() {
|
fn can_parse_raw_abi() {
|
||||||
const VERIFIER_ABI: &str = include_str!("../../tests/solidity-contracts/verifier_abi.json");
|
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();
|
let de = serde_json::to_string(&raw).unwrap();
|
||||||
assert_eq!(abi, serde_json::from_str::<Abi>(&de).unwrap());
|
assert_eq!(abi, serde_json::from_str::<Abi>(&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::<JsonAbi>(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::<JsonAbi>(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{Contract, ContractError};
|
use crate::{Contract, ContractError};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Abi, Token, Tokenize},
|
abi::{Abi, Token, Tokenize},
|
||||||
|
@ -14,8 +15,82 @@ use ethers_core::types::Eip1559TransactionRequest;
|
||||||
|
|
||||||
use std::sync::Arc;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[must_use = "Deployer does nothing unless you `send` it"]
|
||||||
|
pub struct ContractDeployer<M, C> {
|
||||||
|
/// the actual deployer
|
||||||
|
deployer: Deployer<M>,
|
||||||
|
/// marker for the `Contract` type to create afterwards
|
||||||
|
///
|
||||||
|
/// this type will be used to construct it via `From::from(Contract)`
|
||||||
|
_contract: PhantomData<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware, C: From<Contract<M>>> ContractDeployer<M, C> {
|
||||||
|
/// Create a new instance of this [ContractDeployer]
|
||||||
|
pub fn new(deployer: Deployer<M>) -> Self {
|
||||||
|
Self { deployer, _contract: Default::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the number of confirmations to wait for the contract deployment transaction
|
||||||
|
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
|
||||||
|
self.deployer.confs = confirmations.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block<T: Into<BlockNumber>>(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<M>> {
|
||||||
|
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<C, ContractError<M>> {
|
||||||
|
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<M>> {
|
||||||
|
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
|
/// 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<M> {
|
pub struct Deployer<M> {
|
||||||
/// The deployer's transaction, exposed for overriding the defaults
|
/// The deployer's transaction, exposed for overriding the defaults
|
||||||
pub tx: TypedTransaction,
|
pub tx: TypedTransaction,
|
||||||
|
@ -27,20 +102,17 @@ pub struct Deployer<M> {
|
||||||
|
|
||||||
impl<M: Middleware> Deployer<M> {
|
impl<M: Middleware> Deployer<M> {
|
||||||
/// Sets the number of confirmations to wait for the contract deployment transaction
|
/// Sets the number of confirmations to wait for the contract deployment transaction
|
||||||
#[must_use]
|
|
||||||
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
|
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
|
||||||
self.confs = confirmations.into();
|
self.confs = confirmations.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.block = block.into();
|
self.block = block.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment
|
/// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment
|
||||||
#[must_use]
|
|
||||||
pub fn legacy(mut self) -> Self {
|
pub fn legacy(mut self) -> Self {
|
||||||
self.tx = match self.tx {
|
self.tx = match self.tx {
|
||||||
TypedTransaction::Eip1559(inner) => {
|
TypedTransaction::Eip1559(inner) => {
|
||||||
|
@ -109,7 +181,6 @@ impl<M: Middleware> Deployer<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
/// To deploy a contract to the Ethereum network, a `ContractFactory` can be
|
/// To deploy a contract to the Ethereum network, a `ContractFactory` can be
|
||||||
/// created which manages the Contract bytecode and Application Binary Interface
|
/// created which manages the Contract bytecode and Application Binary Interface
|
||||||
/// (ABI), usually generated from the Solidity compiler.
|
/// (ABI), usually generated from the Solidity compiler.
|
||||||
|
@ -151,6 +222,7 @@ impl<M: Middleware> Deployer<M> {
|
||||||
/// println!("{}", contract.address());
|
/// println!("{}", contract.address());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct ContractFactory<M> {
|
pub struct ContractFactory<M> {
|
||||||
client: Arc<M>,
|
client: Arc<M>,
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
|
|
|
@ -10,7 +10,7 @@ mod call;
|
||||||
pub use call::{ContractError, EthCall};
|
pub use call::{ContractError, EthCall};
|
||||||
|
|
||||||
mod factory;
|
mod factory;
|
||||||
pub use factory::ContractFactory;
|
pub use factory::{ContractDeployer, ContractFactory};
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
pub use event::EthEvent;
|
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
|
/// This module exposes low lever builder structures which are only consumed by the
|
||||||
/// type-safe ABI bindings generators.
|
/// type-safe ABI bindings generators.
|
||||||
|
#[doc(hidden)]
|
||||||
pub mod builders {
|
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"))]
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
|
|
|
@ -478,6 +478,24 @@ fn can_handle_case_sensitive_calls() {
|
||||||
let _ = contract.INDEX();
|
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]
|
#[tokio::test]
|
||||||
async fn can_abiencoderv2_output() {
|
async fn can_abiencoderv2_output() {
|
||||||
abigen!(AbiEncoderv2Test, "ethers-contract/tests/solidity-contracts/abiencoderv2test_abi.json",);
|
abigen!(AbiEncoderv2Test, "ethers-contract/tests/solidity-contracts/abiencoderv2test_abi.json",);
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,4 @@
|
||||||
use serde::{
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
de::{Error, Unexpected},
|
|
||||||
Deserialize, Deserializer, Serialize, Serializer,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -77,13 +74,13 @@ impl FromStr for Bytes {
|
||||||
type Err = ParseBytesError;
|
type Err = ParseBytesError;
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
if value.len() >= 2 && &value[0..2] == "0x" {
|
if let Some(value) = value.strip_prefix("0x") {
|
||||||
let bytes: Vec<u8> = hex::decode(&value[2..])
|
hex::decode(value)
|
||||||
.map_err(|e| ParseBytesError(format!("Invalid hex: {}", e)))?;
|
|
||||||
Ok(bytes.into())
|
|
||||||
} else {
|
} 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>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let value = String::deserialize(d)?;
|
let value = String::deserialize(d)?;
|
||||||
if value.len() >= 2 && &value[0..2] == "0x" {
|
if let Some(value) = value.strip_prefix("0x") {
|
||||||
let bytes: Vec<u8> =
|
hex::decode(value)
|
||||||
hex::decode(&value[2..]).map_err(|e| Error::custom(format!("Invalid hex: {}", e)))?;
|
|
||||||
Ok(bytes.into())
|
|
||||||
} else {
|
} 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)]
|
#[cfg(test)]
|
||||||
|
@ -129,7 +126,7 @@ mod tests {
|
||||||
assert_eq!(b.as_ref(), hex::decode("1213").unwrap());
|
assert_eq!(b.as_ref(), hex::decode("1213").unwrap());
|
||||||
|
|
||||||
let b = Bytes::from_str("1213");
|
let b = Bytes::from_str("1213");
|
||||||
assert!(b.is_err());
|
let b = b.unwrap();
|
||||||
assert_eq!(b.err().unwrap().0, "Doesn't start with 0x");
|
assert_eq!(b.as_ref(), hex::decode("1213").unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ mod i256;
|
||||||
pub use i256::{Sign, I256};
|
pub use i256::{Sign, I256};
|
||||||
|
|
||||||
mod bytes;
|
mod bytes;
|
||||||
pub use self::bytes::{Bytes, ParseBytesError};
|
pub use self::bytes::{deserialize_bytes, serialize_bytes, Bytes, ParseBytesError};
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
pub use block::{Block, BlockId, BlockNumber};
|
pub use block::{Block, BlockId, BlockNumber};
|
||||||
|
|
|
@ -7,14 +7,7 @@ pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result<Bytes, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let value = String::deserialize(d)?;
|
String::deserialize(d)?.parse::<Bytes>().map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||||
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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result<Option<Bytes>, D::Error>
|
pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result<Option<Bytes>, D::Error>
|
||||||
|
|
|
@ -3,7 +3,7 @@ use eyre::Result;
|
||||||
use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
|
||||||
|
|
||||||
// Generate the type-safe contract bindings by providing the ABI
|
// Generate the type-safe contract bindings by providing the ABI
|
||||||
// definition in human readable format
|
// definition
|
||||||
abigen!(
|
abigen!(
|
||||||
SimpleContract,
|
SimpleContract,
|
||||||
"./examples/contract_abi.json",
|
"./examples/contract_abi.json",
|
||||||
|
|
|
@ -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::<Http>::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(())
|
||||||
|
}
|
Loading…
Reference in New Issue