Skip to content

Commit

Permalink
Truffle Decoupling (#117)
Browse files Browse the repository at this point in the history
This PR decouples the deployment futures from truffle. This accomplishes two things:
- Sets us up for generating code from non-truffle sources (for example #112)
- Gives us more control over how the generated code (for example #100) 

### Test Plan

CI - This is mostly a refactoring and adding new abstractions

### Commit HIstory

* refactor a bit to uncouple contract from truffle artifacts

* abstracted away need for truffle artifact for deployments

* rename private module

* deployed is now generated with new traits

* refactor deployment traits

* fix unit tests and deploy code

* cargo clippy+fmt

* use Self where it makes sense

* Networks -> Deployments
  • Loading branch information
nlordell authored Jan 14, 2020
1 parent 8400afc commit 6e62a99
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 199 deletions.
2 changes: 1 addition & 1 deletion common/src/truffle/bytecode.rs → common/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl Bytecode {
}

/// Convert a bytecode into its byte representation.
pub fn into_bytes(self) -> Result<Bytes, LinkError> {
pub fn to_bytes(&self) -> Result<Bytes, LinkError> {
match self.undefined_libraries().next() {
Some(library) => Err(LinkError::UndefinedLibrary(library.to_string())),
None => Ok(Bytes(hex::decode(&self.0).expect("valid hex"))),
Expand Down
5 changes: 5 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
//! Crate for common times shared between the `ethcontract` runtime crate as and
//! the `ethcontract-derive` crate.
pub mod bytecode;
pub mod errors;
mod str;
pub mod truffle;

pub use crate::bytecode::Bytecode;
pub use crate::truffle::Artifact;
pub use ethabi::{self as abi, Contract as Abi};
7 changes: 2 additions & 5 deletions common/src/truffle.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
//! Module for reading and examining data produced by truffle.
mod bytecode;

use crate::bytecode::Bytecode;
use crate::errors::ArtifactError;
use ethabi::Contract as Abi;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use web3::types::Address;

pub use self::bytecode::Bytecode;
pub use ethabi::{self as abi, Contract as Abi};

/// Represents a truffle artifact.
#[derive(Clone, Debug, Deserialize)]
pub struct Artifact {
Expand Down
2 changes: 1 addition & 1 deletion generate/src/contract/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub(crate) fn expand(cx: &Context) -> TokenStream {
/// Retrieves the truffle artifact used to generate the type safe API
/// for this contract.
pub fn artifact() -> &'static #ethcontract::Artifact {
use #ethcontract::foreign::lazy_static;
use #ethcontract::private::lazy_static;
use #ethcontract::Artifact;

lazy_static! {
Expand Down
169 changes: 88 additions & 81 deletions generate/src/contract/deployment.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,19 @@
use crate::contract::{methods, Context};
use crate::util;
use anyhow::{Context as _, Result};
use ethcontract_common::truffle::abi::{Param, ParamType};
use ethcontract_common::abi::{Param, ParamType};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;

pub(crate) fn expand(cx: &Context) -> Result<TokenStream> {
let ethcontract = &cx.runtime_crate;
let contract_name = &cx.contract_name;

let deployed = expand_deployed(&cx);
let deploy =
expand_deploy(&cx).context("error generating contract `deploy` associated function")?;

Ok(quote! {
impl #contract_name {
#deployed
#deploy
}

impl #ethcontract::contract::Deploy<#ethcontract::transport::DynTransport> for #contract_name {
fn deployed_at(
web3: #ethcontract::web3::api::Web3<#ethcontract::transport::DynTransport>,
abi: #ethcontract::truffle::Abi,
at: #ethcontract::Address,
) -> Self {
use #ethcontract::Instance;

// NOTE: we need to make sure that we were deployed with the
// correct ABI; luckily Abi implementes PartialEq
debug_assert_eq!(abi, Self::artifact().abi);

Self {
instance: Instance::at(web3, abi, at),
}
}
}
#deployed
#deploy
})
}

Expand All @@ -46,37 +23,50 @@ fn expand_deployed(cx: &Context) -> TokenStream {
}

let ethcontract = &cx.runtime_crate;
let contract_name = &cx.contract_name;

quote! {
/// Locates a deployed contract based on the current network ID
/// reported by the `web3` provider.
///
/// Note that this does not verify that a contract with a maching
/// `Abi` is actually deployed at the given address.
pub fn deployed<F, T>(
web3: &#ethcontract::web3::api::Web3<T>,
) -> #ethcontract::contract::DeployedFuture<#ethcontract::transport::DynTransport, Self>
where
F: #ethcontract::web3::futures::Future<Item = #ethcontract::json::Value, Error = #ethcontract::web3::Error> + Send + 'static,
T: #ethcontract::web3::Transport<Out = F> + 'static,
{
use #ethcontract::Artifact;
use #ethcontract::contract::DeployedFuture;
use #ethcontract::transport::DynTransport;
use #ethcontract::web3::api::Web3;

let transport = DynTransport::new(web3.transport().clone());
let web3 = Web3::new(transport);
let artifact = { // only clone the pieces we need
let artifact = Self::artifact();
Artifact {
abi: artifact.abi.clone(),
networks: artifact.networks.clone(),
..Artifact::empty()
}
};
impl #contract_name {
/// Locates a deployed contract based on the current network ID
/// reported by the `web3` provider.
///
/// Note that this does not verify that a contract with a maching
/// `Abi` is actually deployed at the given address.
pub fn deployed<F, T>(
web3: &#ethcontract::web3::api::Web3<T>,
) -> #ethcontract::contract::DeployedFuture<#ethcontract::transport::DynTransport, Self>
where
F: #ethcontract::web3::futures::Future<
Item = #ethcontract::json::Value,
Error = #ethcontract::web3::Error
> + Send + 'static,
T: #ethcontract::web3::Transport<Out = F> + 'static,
{
use #ethcontract::contract::DeployedFuture;
use #ethcontract::transport::DynTransport;
use #ethcontract::web3::api::Web3;

let transport = DynTransport::new(web3.transport().clone());
let web3 = Web3::new(transport);

DeployedFuture::new(web3, ())
}
}

impl #ethcontract::contract::FromNetwork<#ethcontract::DynTransport> for #contract_name {
type Context = ();

DeployedFuture::from_args(web3, artifact)
fn from_network(web3: #ethcontract::DynWeb3, network_id: &str, _: Self::Context) -> Option<Self> {
use #ethcontract::Instance;

let artifact = Self::artifact();
artifact
.networks
.get(network_id)
.map(move |network| Self {
instance: Instance::at(web3, artifact.abi.clone(), network.address),
})
}
}
}
}
Expand All @@ -88,6 +78,7 @@ fn expand_deploy(cx: &Context) -> Result<TokenStream> {
}

let ethcontract = &cx.runtime_crate;
let contract_name = &cx.contract_name;

// TODO(nlordell): not sure how contructor documentation get generated as I
// can't seem to get truffle to output it
Expand Down Expand Up @@ -123,45 +114,61 @@ fn expand_deploy(cx: &Context) -> Result<TokenStream> {
let address = util::ident(&lib_param.name);

quote! {
artifact.bytecode.link(#name, #address).expect("valid library");
bytecode.link(#name, #address).expect("valid library");
}
});

quote! {
let mut artifact = artifact;
let mut bytecode = bytecode;
#( #link_libraries )*
}
} else {
quote! {}
};

Ok(quote! {
#doc
pub fn builder<F, T>(
web3: &#ethcontract::web3::api::Web3<T> #lib_input #input ,
) -> #ethcontract::DynDeployBuilder<Self>
where
F: #ethcontract::web3::futures::Future<Item = #ethcontract::json::Value, Error = #ethcontract::web3::Error> + Send + 'static,
T: #ethcontract::web3::Transport<Out = F> + 'static,
{
use #ethcontract::{Artifact, DynTransport};
use #ethcontract::contract::DeployBuilder;
use #ethcontract::web3::api::Web3;

let transport = DynTransport::new(web3.transport().clone());
let web3 = Web3::new(transport);

let artifact = { // only clone the pieces we need
let artifact = Self::artifact();
Artifact {
abi: artifact.abi.clone(),
bytecode: artifact.bytecode.clone(),
..Artifact::empty()
}
};
#link
impl #contract_name {
#doc
pub fn builder<F, T>(
web3: &#ethcontract::web3::api::Web3<T> #lib_input #input ,
) -> #ethcontract::DynDeployBuilder<Self>
where
F: #ethcontract::web3::futures::Future<Item = #ethcontract::json::Value, Error = #ethcontract::web3::Error> + Send + 'static,
T: #ethcontract::web3::Transport<Out = F> + 'static,
{
use #ethcontract::DynTransport;
use #ethcontract::contract::DeployBuilder;
use #ethcontract::web3::api::Web3;

let transport = DynTransport::new(web3.transport().clone());
let web3 = Web3::new(transport);

let bytecode = Self::artifact().bytecode.clone();
#link

DeployBuilder::new(web3, bytecode, #arg).expect("valid deployment args")
}
}

DeployBuilder::new(web3, artifact, #arg).expect("valid deployment args")
impl #ethcontract::contract::Deploy<#ethcontract::DynTransport> for #contract_name {
type Context = #ethcontract::common::Bytecode;

fn bytecode(cx: &Self::Context) -> &#ethcontract::common::Bytecode {
cx
}

fn abi(_: &Self::Context) -> &#ethcontract::common::Abi {
&Self::artifact().abi
}

fn at_address(web3: #ethcontract::DynWeb3, address: #ethcontract::Address, _: Self::Context) -> Self {
use #ethcontract::Instance;

let abi = Self::artifact().abi.clone();
Self {
instance: Instance::at(web3, abi, address),
}
}
}
})
}
2 changes: 1 addition & 1 deletion generate/src/contract/methods.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::contract::{types, Context};
use crate::util;
use anyhow::{Context as _, Result};
use ethcontract_common::truffle::abi::{Function, Param};
use ethcontract_common::abi::{Function, Param};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
Expand Down
2 changes: 1 addition & 1 deletion generate/src/contract/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::contract::Context;
use anyhow::{anyhow, Result};
use ethcontract_common::truffle::abi::ParamType;
use ethcontract_common::abi::ParamType;
use proc_macro2::{Literal, TokenStream};
use quote::quote;

Expand Down
62 changes: 51 additions & 11 deletions src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ mod deploy;
mod method;

use crate::errors::{DeployError, LinkError};
use crate::truffle::abi::Result as AbiResult;
use crate::truffle::{Abi, Artifact, Bytecode};
use ethcontract_common::abi::Result as AbiResult;
use ethcontract_common::truffle::Network;
use ethcontract_common::{Abi, Artifact, Bytecode};
use std::collections::HashMap;
use web3::api::Web3;
use web3::contract::tokens::{Detokenize, Tokenize};
use web3::types::{Address, Bytes};
use web3::Transport;

pub use self::deploy::{Deploy, DeployBuilder, DeployFuture, DeployedFuture};
pub use self::deploy::{Deploy, DeployBuilder, DeployFuture, DeployedFuture, FromNetwork};
pub use self::method::{
CallFuture, MethodBuilder, MethodDefaults, MethodFuture, MethodSendFuture, ViewMethodBuilder,
};
Expand Down Expand Up @@ -50,7 +52,7 @@ impl<T: Transport> Instance<T> {
/// Note that this does not verify that a contract with a matchin `Abi` is
/// actually deployed at the given address.
pub fn deployed(web3: Web3<T>, artifact: Artifact) -> DeployedFuture<T, Self> {
DeployedFuture::from_args(web3, artifact)
DeployedFuture::new(web3, Deployments::new(artifact))
}

/// Creates a contract builder with the specified `web3` provider and the
Expand All @@ -64,7 +66,7 @@ impl<T: Transport> Instance<T> {
where
P: Tokenize,
{
DeployBuilder::new(web3, artifact, params)
Linker::new(artifact).deploy(web3, params)
}

/// Deploys a contract with the specified `web3` provider with the given
Expand Down Expand Up @@ -138,6 +140,34 @@ impl<T: Transport> Instance<T> {
}
}

/// Deployment information for for an `Instance`. This includes the contract ABI
/// and the known addresses of contracts for network IDs.
/// be used directly but rather through the `Instance::deployed` API.
#[derive(Debug, Clone)]
pub struct Deployments {
abi: Abi,
networks: HashMap<String, Network>,
}

impl Deployments {
/// Create a new `Deployments` instanced for a contract artifact.
pub fn new(artifact: Artifact) -> Self {
Deployments {
abi: artifact.abi,
networks: artifact.networks,
}
}
}

impl<T: Transport> FromNetwork<T> for Instance<T> {
type Context = Deployments;

fn from_network(web3: Web3<T>, network_id: &str, cx: Self::Context) -> Option<Self> {
let address = cx.networks.get(network_id)?.address;
Some(Instance::at(web3, cx.abi, address))
}
}

/// Builder for specifying linking options for a contract.
#[derive(Debug, Clone)]
pub struct Linker {
Expand Down Expand Up @@ -183,12 +213,22 @@ impl Linker {
T: Transport,
P: Tokenize,
{
let artifact = Artifact {
abi: self.abi,
bytecode: self.bytecode,
..Artifact::empty()
};
DeployBuilder::new(web3, self, params)
}
}

impl<T: Transport> Deploy<T> for Instance<T> {
type Context = Linker;

fn abi(cx: &Self::Context) -> &Abi {
&cx.abi
}

fn bytecode(cx: &Self::Context) -> &Bytecode {
&cx.bytecode
}

DeployBuilder::new(web3, artifact, params)
fn at_address(web3: Web3<T>, address: Address, cx: Self::Context) -> Self {
Instance::at(web3, cx.abi, address)
}
}
Loading

0 comments on commit 6e62a99

Please sign in to comment.