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
|
||||
|
||||
- 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).
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
@ -14,6 +14,9 @@ use ethers_core::{
|
|||
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;
|
||||
|
@ -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 {
|
||||
|
@ -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()?;
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -231,6 +242,7 @@ impl Context {
|
|||
internal_structs,
|
||||
contract_ident,
|
||||
contract_name: args.contract_name,
|
||||
contract_bytecode,
|
||||
method_aliases,
|
||||
event_derives,
|
||||
event_aliases,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<M>(#ethers_contract::Contract<M>);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,11 +106,109 @@ pub struct Component {
|
|||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
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]
|
||||
fn can_parse_raw_abi() {
|
||||
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();
|
||||
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 std::marker::PhantomData;
|
||||
|
||||
use ethers_core::{
|
||||
abi::{Abi, Token, Tokenize},
|
||||
|
@ -14,8 +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<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
|
||||
#[derive(Debug, Clone)]
|
||||
#[must_use = "Deployer does nothing unless you `send` it"]
|
||||
pub struct Deployer<M> {
|
||||
/// The deployer's transaction, exposed for overriding the defaults
|
||||
pub tx: TypedTransaction,
|
||||
|
@ -27,20 +102,17 @@ pub struct Deployer<M> {
|
|||
|
||||
impl<M: Middleware> Deployer<M> {
|
||||
/// 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 {
|
||||
self.confs = confirmations.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn block<T: Into<BlockNumber>>(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) => {
|
||||
|
@ -109,7 +181,6 @@ impl<M: Middleware> Deployer<M> {
|
|||
}
|
||||
}
|
||||
|
||||
#[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 +222,7 @@ impl<M: Middleware> Deployer<M> {
|
|||
/// println!("{}", contract.address());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContractFactory<M> {
|
||||
client: Arc<M>,
|
||||
abi: Abi,
|
||||
|
|
|
@ -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,8 +25,13 @@ 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, event::Event, factory::Deployer};
|
||||
pub use super::{
|
||||
call::ContractCall,
|
||||
event::Event,
|
||||
factory::{ContractDeployer, Deployer},
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "abigen"))]
|
||||
|
|
|
@ -478,6 +478,24 @@ fn can_handle_case_sensitive_calls() {
|
|||
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]
|
||||
async fn can_abiencoderv2_output() {
|
||||
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::{
|
||||
de::{Error, Unexpected},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
use std::{
|
||||
|
@ -77,13 +74,13 @@ impl FromStr for Bytes {
|
|||
type Err = ParseBytesError;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
if value.len() >= 2 && &value[0..2] == "0x" {
|
||||
let bytes: Vec<u8> = 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 +97,13 @@ where
|
|||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(d)?;
|
||||
if value.len() >= 2 && &value[0..2] == "0x" {
|
||||
let bytes: Vec<u8> =
|
||||
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 +126,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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ mod i256;
|
|||
pub use i256::{Sign, I256};
|
||||
|
||||
mod bytes;
|
||||
pub use self::bytes::{Bytes, ParseBytesError};
|
||||
pub use self::bytes::{deserialize_bytes, serialize_bytes, Bytes, ParseBytesError};
|
||||
|
||||
mod block;
|
||||
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
|
||||
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::<Bytes>().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>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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