abigen: simplify structs and re-enable file/remote codegen
This commit is contained in:
parent
b47c89455c
commit
ba7fedc7d3
|
@ -0,0 +1 @@
|
||||||
|
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]
|
|
@ -1,10 +1,8 @@
|
||||||
use ethers_contract_abigen::Builder;
|
use ethers_contract_abigen::Abigen;
|
||||||
|
|
||||||
const ABI: &str = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let builder = Builder::from_str("ERC20Token", ABI);
|
Abigen::new("ERC20Token", "./abi.json")
|
||||||
builder
|
.unwrap()
|
||||||
.generate()
|
.generate()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_to_file("token.rs")
|
.write_to_file("token.rs")
|
||||||
|
|
|
@ -10,7 +10,7 @@ mod methods;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
use super::util;
|
use super::util;
|
||||||
use super::Args;
|
use super::Abigen;
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use ethers_core::{abi::Abi, types::Address};
|
use ethers_core::{abi::Abi, types::Address};
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
|
@ -21,23 +21,12 @@ use syn::{Path, Visibility};
|
||||||
|
|
||||||
/// Internal shared context for generating smart contract bindings.
|
/// Internal shared context for generating smart contract bindings.
|
||||||
pub(crate) struct Context {
|
pub(crate) struct Context {
|
||||||
/// The contract name
|
|
||||||
name: String,
|
|
||||||
|
|
||||||
/// The ABI string pre-parsing.
|
/// The ABI string pre-parsing.
|
||||||
abi_str: Literal,
|
abi_str: Literal,
|
||||||
|
|
||||||
/// The parsed ABI.
|
/// The parsed ABI.
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
|
|
||||||
/// The identifier for the runtime crate. Usually this is `ethcontract` but
|
|
||||||
/// it can be different if the crate was renamed in the Cargo manifest for
|
|
||||||
/// example.
|
|
||||||
runtime_crate: Ident,
|
|
||||||
|
|
||||||
/// The visibility for the generated module and re-exported contract type.
|
|
||||||
visibility: Visibility,
|
|
||||||
|
|
||||||
/// The contract name as an identifier.
|
/// The contract name as an identifier.
|
||||||
contract_name: Ident,
|
contract_name: Ident,
|
||||||
|
|
||||||
|
@ -49,8 +38,8 @@ pub(crate) struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn expand(args: Args) -> Result<TokenStream> {
|
pub(crate) fn expand(args: Abigen) -> Result<TokenStream> {
|
||||||
let cx = Self::from_args(args)?;
|
let cx = Self::from_abigen(args)?;
|
||||||
let name = &cx.contract_name;
|
let name = &cx.contract_name;
|
||||||
let name_mod = util::ident(&format!(
|
let name_mod = util::ident(&format!(
|
||||||
"{}_mod",
|
"{}_mod",
|
||||||
|
@ -105,7 +94,7 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a context from the code generation arguments.
|
/// Create a context from the code generation arguments.
|
||||||
fn from_args(args: Args) -> Result<Self> {
|
fn from_abigen(args: Abigen) -> Result<Self> {
|
||||||
// get the actual ABI string
|
// get the actual ABI string
|
||||||
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
|
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
|
||||||
|
|
||||||
|
@ -116,16 +105,7 @@ impl Context {
|
||||||
format!("failed to parse artifact from source {:?}", args.abi_source,)
|
format!("failed to parse artifact from source {:?}", args.abi_source,)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let raw_contract_name = args.contract_name;
|
let contract_name = util::ident(&args.contract_name);
|
||||||
|
|
||||||
let runtime_crate = util::ident(&args.runtime_crate_name);
|
|
||||||
|
|
||||||
let visibility = match args.visibility_modifier.as_ref() {
|
|
||||||
Some(vis) => syn::parse_str(vis)?,
|
|
||||||
None => Visibility::Inherited,
|
|
||||||
};
|
|
||||||
|
|
||||||
let contract_name = util::ident(&raw_contract_name);
|
|
||||||
|
|
||||||
// NOTE: We only check for duplicate signatures here, since if there are
|
// NOTE: We only check for duplicate signatures here, since if there are
|
||||||
// duplicate aliases, the compiler will produce a warning because a
|
// duplicate aliases, the compiler will produce a warning because a
|
||||||
|
@ -149,11 +129,8 @@ impl Context {
|
||||||
.context("failed to parse event derives")?;
|
.context("failed to parse event derives")?;
|
||||||
|
|
||||||
Ok(Context {
|
Ok(Context {
|
||||||
name: raw_contract_name,
|
|
||||||
abi,
|
abi,
|
||||||
abi_str: Literal::string(&abi_str),
|
abi_str: Literal::string(&abi_str),
|
||||||
runtime_crate,
|
|
||||||
visibility,
|
|
||||||
contract_name,
|
contract_name,
|
||||||
method_aliases,
|
method_aliases,
|
||||||
event_derives,
|
event_derives,
|
||||||
|
|
|
@ -88,9 +88,9 @@ fn expand_event(event: &Event, event_derives: &[Path]) -> Result<TokenStream> {
|
||||||
let params_len = Literal::usize_unsuffixed(params.len());
|
let params_len = Literal::usize_unsuffixed(params.len());
|
||||||
let read_param_token = params
|
let read_param_token = params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, ty)| {
|
.map(|(name, _)| {
|
||||||
quote! {
|
quote! {
|
||||||
let #name = #ty::from_token(tokens.next().expect("this should never happen"))?;
|
let #name = Detokenize::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -25,14 +25,11 @@ pub use util::parse_address;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fs::File, io::Write, path::Path};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
/// Internal global arguments passed to the generators for each individual
|
/// Internal global arguments passed to the generators for each individual
|
||||||
/// component that control expansion.
|
/// component that control expansion.
|
||||||
pub(crate) struct Args {
|
pub struct Abigen {
|
||||||
/// The source of the ABI JSON for the contract whose bindings
|
/// The source of the ABI JSON for the contract whose bindings
|
||||||
/// are being generated.
|
/// are being generated.
|
||||||
abi_source: Source,
|
abi_source: Source,
|
||||||
|
@ -40,120 +37,27 @@ pub(crate) struct Args {
|
||||||
/// Override the contract name to use for the generated type.
|
/// Override the contract name to use for the generated type.
|
||||||
contract_name: String,
|
contract_name: String,
|
||||||
|
|
||||||
/// The runtime crate name to use.
|
|
||||||
runtime_crate_name: String,
|
|
||||||
|
|
||||||
/// The visibility modifier to use for the generated module and contract
|
|
||||||
/// re-export.
|
|
||||||
visibility_modifier: Option<String>,
|
|
||||||
|
|
||||||
/// Override the contract module name that contains the generated code.
|
|
||||||
contract_mod_override: Option<String>,
|
|
||||||
|
|
||||||
/// Manually specified contract method aliases.
|
/// Manually specified contract method aliases.
|
||||||
method_aliases: HashMap<String, String>,
|
method_aliases: HashMap<String, String>,
|
||||||
|
|
||||||
/// Derives added to event structs and enums.
|
/// Derives added to event structs and enums.
|
||||||
event_derives: Vec<String>,
|
event_derives: Vec<String>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Args {
|
|
||||||
/// Creates a new builder given the path to a contract's truffle artifact
|
|
||||||
/// JSON file.
|
|
||||||
pub fn new(contract_name: &str, abi_source: Source) -> Self {
|
|
||||||
Args {
|
|
||||||
abi_source,
|
|
||||||
contract_name: contract_name.to_owned(),
|
|
||||||
|
|
||||||
runtime_crate_name: "abigen".to_owned(),
|
|
||||||
visibility_modifier: None,
|
|
||||||
contract_mod_override: None,
|
|
||||||
method_aliases: HashMap::new(),
|
|
||||||
event_derives: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal output options for controlling how the generated code gets
|
|
||||||
/// serialized to file.
|
|
||||||
struct SerializationOptions {
|
|
||||||
/// Format the code using a locally installed copy of `rustfmt`.
|
/// Format the code using a locally installed copy of `rustfmt`.
|
||||||
rustfmt: bool,
|
rustfmt: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SerializationOptions {
|
impl Abigen {
|
||||||
fn default() -> Self {
|
|
||||||
SerializationOptions { rustfmt: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builder for generating contract code. Note that no code is generated until
|
|
||||||
/// the builder is finalized with `generate` or `output`.
|
|
||||||
pub struct Builder {
|
|
||||||
/// The contract binding generation args.
|
|
||||||
args: Args,
|
|
||||||
/// The serialization options.
|
|
||||||
options: SerializationOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
/// Creates a new builder given the contract's ABI JSON string
|
|
||||||
pub fn from_str(name: &str, abi: &str) -> Self {
|
|
||||||
Builder::source(name, Source::String(abi.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new builder given the path to a contract's ABI file
|
|
||||||
pub fn new<P>(name: &str, path: P) -> Self
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
Builder::source(name, Source::local(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new builder from a source URL.
|
|
||||||
pub fn from_url<S>(name: &str, url: S) -> Result<Self>
|
|
||||||
where
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
let source = Source::parse(url)?;
|
|
||||||
Ok(Builder::source(name, source))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new builder with the given ABI JSON source.
|
/// Creates a new builder with the given ABI JSON source.
|
||||||
pub fn source(name: &str, source: Source) -> Self {
|
pub fn new<S: AsRef<str>>(contract_name: &str, abi_source: S) -> Result<Self> {
|
||||||
Builder {
|
let abi_source = abi_source.as_ref().parse()?;
|
||||||
args: Args::new(name, source),
|
Ok(Self {
|
||||||
options: SerializationOptions::default(),
|
abi_source,
|
||||||
}
|
contract_name: contract_name.to_owned(),
|
||||||
}
|
method_aliases: HashMap::new(),
|
||||||
|
event_derives: Vec::new(),
|
||||||
/// Sets the crate name for the runtime crate. This setting is usually only
|
rustfmt: true,
|
||||||
/// needed if the crate was renamed in the Cargo manifest.
|
})
|
||||||
pub fn runtime_crate_name<S>(mut self, name: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.args.runtime_crate_name = name.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets an optional visibility modifier for the generated module and
|
|
||||||
/// contract re-export.
|
|
||||||
pub fn visibility_modifier<S>(mut self, vis: Option<S>) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.args.visibility_modifier = vis.map(S::into);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the optional contract module name override.
|
|
||||||
pub fn contract_mod_override<S>(mut self, name: Option<S>) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.args.contract_mod_override = name.map(S::into);
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually adds a solidity method alias to specify what the method name
|
/// Manually adds a solidity method alias to specify what the method name
|
||||||
|
@ -164,9 +68,7 @@ impl Builder {
|
||||||
S1: Into<String>,
|
S1: Into<String>,
|
||||||
S2: Into<String>,
|
S2: Into<String>,
|
||||||
{
|
{
|
||||||
self.args
|
self.method_aliases.insert(signature.into(), alias.into());
|
||||||
.method_aliases
|
|
||||||
.insert(signature.into(), alias.into());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +78,7 @@ impl Builder {
|
||||||
/// Note that in case `rustfmt` does not exist or produces an error, the
|
/// Note that in case `rustfmt` does not exist or produces an error, the
|
||||||
/// unformatted code will be used.
|
/// unformatted code will be used.
|
||||||
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
||||||
self.options.rustfmt = rustfmt;
|
self.rustfmt = rustfmt;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,17 +90,15 @@ impl Builder {
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
self.args.event_derives.push(derive.into());
|
self.event_derives.push(derive.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the contract bindings.
|
/// Generates the contract bindings.
|
||||||
pub fn generate(self) -> Result<ContractBindings> {
|
pub fn generate(self) -> Result<ContractBindings> {
|
||||||
let tokens = Context::expand(self.args)?;
|
let rustfmt = self.rustfmt;
|
||||||
Ok(ContractBindings {
|
let tokens = Context::expand(self)?;
|
||||||
tokens,
|
Ok(ContractBindings { tokens, rustfmt })
|
||||||
options: self.options,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +108,7 @@ pub struct ContractBindings {
|
||||||
/// The TokenStream representing the contract bindings.
|
/// The TokenStream representing the contract bindings.
|
||||||
tokens: TokenStream,
|
tokens: TokenStream,
|
||||||
/// The output options used for serialization.
|
/// The output options used for serialization.
|
||||||
options: SerializationOptions,
|
rustfmt: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContractBindings {
|
impl ContractBindings {
|
||||||
|
@ -220,7 +120,7 @@ impl ContractBindings {
|
||||||
let source = {
|
let source = {
|
||||||
let raw = self.tokens.to_string();
|
let raw = self.tokens.to_string();
|
||||||
|
|
||||||
if self.options.rustfmt {
|
if self.rustfmt {
|
||||||
rustfmt::format(&raw).unwrap_or(raw)
|
rustfmt::format(&raw).unwrap_or(raw)
|
||||||
} else {
|
} else {
|
||||||
raw
|
raw
|
||||||
|
|
|
@ -35,17 +35,24 @@ impl Source {
|
||||||
/// Parses an ABI from a source
|
/// Parses an ABI from a source
|
||||||
///
|
///
|
||||||
/// Contract ABIs can be retrieved from the local filesystem or online
|
/// Contract ABIs can be retrieved from the local filesystem or online
|
||||||
/// from `etherscan.io`, this method parses ABI source URLs and accepts
|
/// from `etherscan.io`. They can also be provided in-line. This method parses
|
||||||
/// the following:
|
/// ABI source URLs and accepts the following:
|
||||||
|
///
|
||||||
|
/// - raw ABI JSON
|
||||||
|
///
|
||||||
/// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file.
|
/// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file.
|
||||||
/// This relative path is rooted in the current working directory.
|
/// This relative path is rooted in the current working directory.
|
||||||
/// To specify the root for relative paths, use `Source::with_root`.
|
/// To specify the root for relative paths, use `Source::with_root`.
|
||||||
|
///
|
||||||
/// - `/absolute/path/to/Contract.json` or
|
/// - `/absolute/path/to/Contract.json` or
|
||||||
/// `file:///absolute/path/to/Contract.json`: an absolute path or file URL
|
/// `file:///absolute/path/to/Contract.json`: an absolute path or file URL
|
||||||
/// to an ABI JSON file.
|
/// to an ABI JSON file.
|
||||||
|
///
|
||||||
/// - `http(s)://...` an HTTP url to a contract ABI.
|
/// - `http(s)://...` an HTTP url to a contract ABI.
|
||||||
|
///
|
||||||
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a
|
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a
|
||||||
/// address or URL of a verified contract on Etherscan.
|
/// address or URL of a verified contract on Etherscan.
|
||||||
|
///
|
||||||
/// - `npm:@org/package@1.0.0/path/to/contract.json` an npmjs package with
|
/// - `npm:@org/package@1.0.0/path/to/contract.json` an npmjs package with
|
||||||
/// an optional version and path (defaulting to the latest version and
|
/// an optional version and path (defaulting to the latest version and
|
||||||
/// `index.js`). The contract ABI will be retrieved through
|
/// `index.js`). The contract ABI will be retrieved through
|
||||||
|
@ -54,6 +61,10 @@ impl Source {
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
|
let source = source.as_ref();
|
||||||
|
if source.starts_with('[') {
|
||||||
|
return Ok(Source::String(source.to_owned()));
|
||||||
|
}
|
||||||
let root = env::current_dir()?.canonicalize()?;
|
let root = env::current_dir()?.canonicalize()?;
|
||||||
Source::with_root(root, source)
|
Source::with_root(root, source)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +72,7 @@ impl Source {
|
||||||
/// Parses an artifact source from a string and a specified root directory
|
/// Parses an artifact source from a string and a specified root directory
|
||||||
/// for resolving relative paths. See `Source::with_root` for more details
|
/// for resolving relative paths. See `Source::with_root` for more details
|
||||||
/// on supported source strings.
|
/// on supported source strings.
|
||||||
pub fn with_root<P, S>(root: P, source: S) -> Result<Self>
|
fn with_root<P, S>(root: P, source: S) -> Result<Self>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -88,7 +99,7 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a local filesystem source from a path string.
|
/// Creates a local filesystem source from a path string.
|
||||||
pub fn local<P>(path: P) -> Self
|
fn local<P>(path: P) -> Self
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
@ -96,7 +107,7 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an HTTP source from a URL.
|
/// Creates an HTTP source from a URL.
|
||||||
pub fn http<S>(url: S) -> Result<Self>
|
fn http<S>(url: S) -> Result<Self>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
|
@ -104,7 +115,7 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an Etherscan source from an address string.
|
/// Creates an Etherscan source from an address string.
|
||||||
pub fn etherscan<S>(address: S) -> Result<Self>
|
fn etherscan<S>(address: S) -> Result<Self>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
|
@ -114,7 +125,7 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an Etherscan source from an address string.
|
/// Creates an Etherscan source from an address string.
|
||||||
pub fn npm<S>(package_path: S) -> Self
|
fn npm<S>(package_path: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
|
@ -161,14 +172,14 @@ fn get_local_contract(path: &Path) -> Result<String> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = fs::read_to_string(path).context("failed to read artifact JSON file")?;
|
let json = fs::read_to_string(path).context("failed to read artifact JSON file")?;
|
||||||
Ok(abi_or_artifact(json))
|
Ok(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a Truffle artifact or ABI from an HTTP URL.
|
/// Retrieves a Truffle artifact or ABI from an HTTP URL.
|
||||||
fn get_http_contract(url: &Url) -> Result<String> {
|
fn get_http_contract(url: &Url) -> Result<String> {
|
||||||
let json = util::http_get(url.as_str())
|
let json = util::http_get(url.as_str())
|
||||||
.with_context(|| format!("failed to retrieve JSON from {}", url))?;
|
.with_context(|| format!("failed to retrieve JSON from {}", url))?;
|
||||||
Ok(abi_or_artifact(json))
|
Ok(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a contract ABI from the Etherscan HTTP API and wraps it in an
|
/// Retrieves a contract ABI from the Etherscan HTTP API and wraps it in an
|
||||||
|
@ -188,16 +199,7 @@ fn get_etherscan_contract(address: Address) -> Result<String> {
|
||||||
address, api_key,
|
address, api_key,
|
||||||
);
|
);
|
||||||
let abi = util::http_get(&abi_url).context("failed to retrieve ABI from Etherscan.io")?;
|
let abi = util::http_get(&abi_url).context("failed to retrieve ABI from Etherscan.io")?;
|
||||||
|
Ok(abi)
|
||||||
// NOTE: Wrap the retrieved ABI in an empty contract, this is because
|
|
||||||
// currently, the code generation infrastructure depends on having an
|
|
||||||
// `Artifact` instance.
|
|
||||||
let json = format!(
|
|
||||||
r#"{{"abi":{},"networks":{{"1":{{"address":"{:?}"}}}}}}"#,
|
|
||||||
abi, address,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a Truffle artifact or ABI from an npm package through `unpkg.io`.
|
/// Retrieves a Truffle artifact or ABI from an npm package through `unpkg.io`.
|
||||||
|
@ -206,25 +208,7 @@ fn get_npm_contract(package: &str) -> Result<String> {
|
||||||
let json = util::http_get(&unpkg_url)
|
let json = util::http_get(&unpkg_url)
|
||||||
.with_context(|| format!("failed to retrieve JSON from for npm package {}", package))?;
|
.with_context(|| format!("failed to retrieve JSON from for npm package {}", package))?;
|
||||||
|
|
||||||
Ok(abi_or_artifact(json))
|
Ok(json)
|
||||||
}
|
|
||||||
|
|
||||||
/// A best-effort coersion of an ABI or Truffle artifact JSON document into a
|
|
||||||
/// Truffle artifact JSON document.
|
|
||||||
///
|
|
||||||
/// This method uses the fact that ABIs are arrays and Truffle artifacts are
|
|
||||||
/// objects to guess at what type of document this is. Note that no parsing or
|
|
||||||
/// validation is done at this point as the document gets parsed and validated
|
|
||||||
/// at generation time.
|
|
||||||
///
|
|
||||||
/// This needs to be done as currently the contract generation infrastructure
|
|
||||||
/// depends on having a Truffle artifact.
|
|
||||||
fn abi_or_artifact(json: String) -> String {
|
|
||||||
if json.trim().starts_with('[') {
|
|
||||||
format!(r#"{{"abi":{}}}"#, json.trim())
|
|
||||||
} else {
|
|
||||||
json
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -263,5 +247,9 @@ mod tests {
|
||||||
let source = Source::with_root(root, url).unwrap();
|
let source = Source::with_root(root, url).unwrap();
|
||||||
assert_eq!(source, *expected);
|
assert_eq!(source, *expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let src = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#;
|
||||||
|
let parsed = Source::parse(src).unwrap();
|
||||||
|
assert_eq!(parsed, Source::String(src.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue