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:
Matthias Seitz 2022-03-19 05:23:33 +01:00 committed by GitHub
parent 262149945a
commit b6b5b09f4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 469 additions and 63 deletions

View File

@ -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).

View File

@ -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,
}
}
}

View File

@ -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>);

View File

@ -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,

View File

@ -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);
}
} }

View File

@ -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,

View File

@ -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"))]

View File

@ -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

View File

@ -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());
} }
} }

View File

@ -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};

View File

@ -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>

View File

@ -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",

View File

@ -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(())
}