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;
|
||||
|
||||
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"}]"#;
|
||||
use ethers_contract_abigen::Abigen;
|
||||
|
||||
fn main() {
|
||||
let builder = Builder::from_str("ERC20Token", ABI);
|
||||
builder
|
||||
Abigen::new("ERC20Token", "./abi.json")
|
||||
.unwrap()
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file("token.rs")
|
||||
|
|
|
@ -10,7 +10,7 @@ mod methods;
|
|||
mod types;
|
||||
|
||||
use super::util;
|
||||
use super::Args;
|
||||
use super::Abigen;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use ethers_core::{abi::Abi, types::Address};
|
||||
use inflector::Inflector;
|
||||
|
@ -21,23 +21,12 @@ use syn::{Path, Visibility};
|
|||
|
||||
/// Internal shared context for generating smart contract bindings.
|
||||
pub(crate) struct Context {
|
||||
/// The contract name
|
||||
name: String,
|
||||
|
||||
/// The ABI string pre-parsing.
|
||||
abi_str: Literal,
|
||||
|
||||
/// The parsed 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.
|
||||
contract_name: Ident,
|
||||
|
||||
|
@ -49,8 +38,8 @@ pub(crate) struct Context {
|
|||
}
|
||||
|
||||
impl Context {
|
||||
pub fn expand(args: Args) -> Result<TokenStream> {
|
||||
let cx = Self::from_args(args)?;
|
||||
pub(crate) fn expand(args: Abigen) -> Result<TokenStream> {
|
||||
let cx = Self::from_abigen(args)?;
|
||||
let name = &cx.contract_name;
|
||||
let name_mod = util::ident(&format!(
|
||||
"{}_mod",
|
||||
|
@ -105,7 +94,7 @@ impl Context {
|
|||
}
|
||||
|
||||
/// 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
|
||||
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,)
|
||||
})?;
|
||||
|
||||
let raw_contract_name = 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);
|
||||
let contract_name = util::ident(&args.contract_name);
|
||||
|
||||
// NOTE: We only check for duplicate signatures here, since if there are
|
||||
// duplicate aliases, the compiler will produce a warning because a
|
||||
|
@ -149,11 +129,8 @@ impl Context {
|
|||
.context("failed to parse event derives")?;
|
||||
|
||||
Ok(Context {
|
||||
name: raw_contract_name,
|
||||
abi,
|
||||
abi_str: Literal::string(&abi_str),
|
||||
runtime_crate,
|
||||
visibility,
|
||||
contract_name,
|
||||
method_aliases,
|
||||
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 read_param_token = params
|
||||
.iter()
|
||||
.map(|(name, ty)| {
|
||||
.map(|(name, _)| {
|
||||
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<_>>();
|
||||
|
|
|
@ -25,14 +25,11 @@ pub use util::parse_address;
|
|||
|
||||
use anyhow::Result;
|
||||
use proc_macro2::TokenStream;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::{collections::HashMap, fs::File, io::Write, path::Path};
|
||||
|
||||
/// Internal global arguments passed to the generators for each individual
|
||||
/// component that control expansion.
|
||||
pub(crate) struct Args {
|
||||
pub struct Abigen {
|
||||
/// The source of the ABI JSON for the contract whose bindings
|
||||
/// are being generated.
|
||||
abi_source: Source,
|
||||
|
@ -40,120 +37,27 @@ pub(crate) struct Args {
|
|||
/// Override the contract name to use for the generated type.
|
||||
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.
|
||||
method_aliases: HashMap<String, String>,
|
||||
|
||||
/// Derives added to event structs and enums.
|
||||
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`.
|
||||
rustfmt: bool,
|
||||
}
|
||||
|
||||
impl Default for SerializationOptions {
|
||||
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))
|
||||
}
|
||||
|
||||
impl Abigen {
|
||||
/// Creates a new builder with the given ABI JSON source.
|
||||
pub fn source(name: &str, source: Source) -> Self {
|
||||
Builder {
|
||||
args: Args::new(name, source),
|
||||
options: SerializationOptions::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the crate name for the runtime crate. This setting is usually only
|
||||
/// 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
|
||||
pub fn new<S: AsRef<str>>(contract_name: &str, abi_source: S) -> Result<Self> {
|
||||
let abi_source = abi_source.as_ref().parse()?;
|
||||
Ok(Self {
|
||||
abi_source,
|
||||
contract_name: contract_name.to_owned(),
|
||||
method_aliases: HashMap::new(),
|
||||
event_derives: Vec::new(),
|
||||
rustfmt: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Manually adds a solidity method alias to specify what the method name
|
||||
|
@ -164,9 +68,7 @@ impl Builder {
|
|||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
self.args
|
||||
.method_aliases
|
||||
.insert(signature.into(), alias.into());
|
||||
self.method_aliases.insert(signature.into(), alias.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -176,7 +78,7 @@ impl Builder {
|
|||
/// Note that in case `rustfmt` does not exist or produces an error, the
|
||||
/// unformatted code will be used.
|
||||
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
||||
self.options.rustfmt = rustfmt;
|
||||
self.rustfmt = rustfmt;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -188,17 +90,15 @@ impl Builder {
|
|||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.args.event_derives.push(derive.into());
|
||||
self.event_derives.push(derive.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Generates the contract bindings.
|
||||
pub fn generate(self) -> Result<ContractBindings> {
|
||||
let tokens = Context::expand(self.args)?;
|
||||
Ok(ContractBindings {
|
||||
tokens,
|
||||
options: self.options,
|
||||
})
|
||||
let rustfmt = self.rustfmt;
|
||||
let tokens = Context::expand(self)?;
|
||||
Ok(ContractBindings { tokens, rustfmt })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +108,7 @@ pub struct ContractBindings {
|
|||
/// The TokenStream representing the contract bindings.
|
||||
tokens: TokenStream,
|
||||
/// The output options used for serialization.
|
||||
options: SerializationOptions,
|
||||
rustfmt: bool,
|
||||
}
|
||||
|
||||
impl ContractBindings {
|
||||
|
@ -220,7 +120,7 @@ impl ContractBindings {
|
|||
let source = {
|
||||
let raw = self.tokens.to_string();
|
||||
|
||||
if self.options.rustfmt {
|
||||
if self.rustfmt {
|
||||
rustfmt::format(&raw).unwrap_or(raw)
|
||||
} else {
|
||||
raw
|
||||
|
|
|
@ -35,17 +35,24 @@ impl Source {
|
|||
/// Parses an ABI from a source
|
||||
///
|
||||
/// Contract ABIs can be retrieved from the local filesystem or online
|
||||
/// from `etherscan.io`, this method parses ABI source URLs and accepts
|
||||
/// the following:
|
||||
/// from `etherscan.io`. They can also be provided in-line. This method parses
|
||||
/// ABI source URLs and accepts the following:
|
||||
///
|
||||
/// - raw ABI JSON
|
||||
///
|
||||
/// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file.
|
||||
/// This relative path is rooted in the current working directory.
|
||||
/// To specify the root for relative paths, use `Source::with_root`.
|
||||
///
|
||||
/// - `/absolute/path/to/Contract.json` or
|
||||
/// `file:///absolute/path/to/Contract.json`: an absolute path or file URL
|
||||
/// to an ABI JSON file.
|
||||
///
|
||||
/// - `http(s)://...` an HTTP url to a contract ABI.
|
||||
///
|
||||
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a
|
||||
/// address or URL of a verified contract on Etherscan.
|
||||
///
|
||||
/// - `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
|
||||
/// `index.js`). The contract ABI will be retrieved through
|
||||
|
@ -54,6 +61,10 @@ impl Source {
|
|||
where
|
||||
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()?;
|
||||
Source::with_root(root, source)
|
||||
}
|
||||
|
@ -61,7 +72,7 @@ impl Source {
|
|||
/// Parses an artifact source from a string and a specified root directory
|
||||
/// for resolving relative paths. See `Source::with_root` for more details
|
||||
/// 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
|
||||
P: AsRef<Path>,
|
||||
S: AsRef<str>,
|
||||
|
@ -88,7 +99,7 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Creates a local filesystem source from a path string.
|
||||
pub fn local<P>(path: P) -> Self
|
||||
fn local<P>(path: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
@ -96,7 +107,7 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Creates an HTTP source from a URL.
|
||||
pub fn http<S>(url: S) -> Result<Self>
|
||||
fn http<S>(url: S) -> Result<Self>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
|
@ -104,7 +115,7 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Creates an Etherscan source from an address string.
|
||||
pub fn etherscan<S>(address: S) -> Result<Self>
|
||||
fn etherscan<S>(address: S) -> Result<Self>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
|
@ -114,7 +125,7 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Creates an Etherscan source from an address string.
|
||||
pub fn npm<S>(package_path: S) -> Self
|
||||
fn npm<S>(package_path: S) -> Self
|
||||
where
|
||||
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")?;
|
||||
Ok(abi_or_artifact(json))
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
/// Retrieves a Truffle artifact or ABI from an HTTP URL.
|
||||
fn get_http_contract(url: &Url) -> Result<String> {
|
||||
let json = util::http_get(url.as_str())
|
||||
.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
|
||||
|
@ -188,16 +199,7 @@ fn get_etherscan_contract(address: Address) -> Result<String> {
|
|||
address, api_key,
|
||||
);
|
||||
let abi = util::http_get(&abi_url).context("failed to retrieve ABI from Etherscan.io")?;
|
||||
|
||||
// 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)
|
||||
Ok(abi)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
.with_context(|| format!("failed to retrieve JSON from for npm package {}", package))?;
|
||||
|
||||
Ok(abi_or_artifact(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
|
||||
}
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -263,5 +247,9 @@ mod tests {
|
|||
let source = Source::with_root(root, url).unwrap();
|
||||
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