2020-05-26 18:57:59 +00:00
|
|
|
#![deny(missing_docs)]
|
|
|
|
mod common;
|
2022-08-02 18:03:52 +00:00
|
|
|
mod errors;
|
2020-05-26 18:57:59 +00:00
|
|
|
mod events;
|
|
|
|
mod methods;
|
2022-09-08 16:07:38 +00:00
|
|
|
pub(crate) mod structs;
|
2020-05-26 18:57:59 +00:00
|
|
|
mod types;
|
|
|
|
|
2021-10-29 12:29:35 +00:00
|
|
|
use super::{util, Abigen};
|
2022-09-29 18:15:04 +00:00
|
|
|
use crate::contract::{methods::MethodAlias, structs::InternalStructs};
|
2021-11-05 13:00:01 +00:00
|
|
|
use ethers_core::{
|
2022-09-29 18:15:04 +00:00
|
|
|
abi::{Abi, AbiParser, ErrorExt, EventExt, JsonAbi},
|
2021-11-05 13:00:01 +00:00
|
|
|
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
|
2022-08-02 18:03:52 +00:00
|
|
|
types::Bytes,
|
2021-11-05 13:00:01 +00:00
|
|
|
};
|
2022-02-02 20:44:53 +00:00
|
|
|
use eyre::{eyre, Context as _, Result};
|
2020-05-26 18:57:59 +00:00
|
|
|
use proc_macro2::{Ident, Literal, TokenStream};
|
|
|
|
use quote::quote;
|
2021-10-02 17:16:55 +00:00
|
|
|
use serde::Deserialize;
|
2021-02-19 06:34:56 +00:00
|
|
|
use std::collections::BTreeMap;
|
2021-10-10 08:31:34 +00:00
|
|
|
use syn::Path;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2021-10-11 14:18:09 +00:00
|
|
|
/// The result of `Context::expand`
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ExpandedContract {
|
|
|
|
/// The name of the contract module
|
|
|
|
pub module: Ident,
|
|
|
|
/// The contract module's imports
|
|
|
|
pub imports: TokenStream,
|
|
|
|
/// Contract, Middle related implementations
|
|
|
|
pub contract: TokenStream,
|
|
|
|
/// All event impls of the contract
|
|
|
|
pub events: TokenStream,
|
2022-08-02 18:03:52 +00:00
|
|
|
/// All error impls of the contract
|
|
|
|
pub errors: TokenStream,
|
2021-10-18 10:28:38 +00:00
|
|
|
/// All contract call struct related types
|
|
|
|
pub call_structs: TokenStream,
|
2021-10-11 14:18:09 +00:00
|
|
|
/// The contract's internal structs
|
|
|
|
pub abi_structs: TokenStream,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ExpandedContract {
|
|
|
|
/// Merges everything into a single module
|
|
|
|
pub fn into_tokens(self) -> TokenStream {
|
2022-08-02 18:03:52 +00:00
|
|
|
let ExpandedContract {
|
|
|
|
module,
|
|
|
|
imports,
|
|
|
|
contract,
|
|
|
|
events,
|
|
|
|
call_structs,
|
|
|
|
abi_structs,
|
|
|
|
errors,
|
|
|
|
} = self;
|
2021-10-11 14:18:09 +00:00
|
|
|
quote! {
|
|
|
|
// export all the created data types
|
|
|
|
pub use #module::*;
|
|
|
|
|
2022-05-27 20:32:57 +00:00
|
|
|
#[allow(clippy::too_many_arguments, non_camel_case_types)]
|
2022-05-31 20:29:02 +00:00
|
|
|
pub mod #module {
|
2021-10-11 14:18:09 +00:00
|
|
|
#imports
|
|
|
|
#contract
|
2022-08-02 18:03:52 +00:00
|
|
|
#errors
|
2021-10-11 14:18:09 +00:00
|
|
|
#events
|
2021-10-18 10:28:38 +00:00
|
|
|
#call_structs
|
2021-10-11 14:18:09 +00:00
|
|
|
#abi_structs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
/// Internal shared context for generating smart contract bindings.
|
2021-10-11 14:18:09 +00:00
|
|
|
pub struct Context {
|
2020-05-26 18:57:59 +00:00
|
|
|
/// The ABI string pre-parsing.
|
|
|
|
abi_str: Literal,
|
|
|
|
|
|
|
|
/// The parsed ABI.
|
|
|
|
abi: Abi,
|
|
|
|
|
2021-03-16 19:37:19 +00:00
|
|
|
/// The parser used for human readable format
|
|
|
|
abi_parser: AbiParser,
|
|
|
|
|
2021-08-16 07:29:44 +00:00
|
|
|
/// Contains all the solidity structs extracted from the JSON ABI.
|
|
|
|
internal_structs: InternalStructs,
|
|
|
|
|
2020-10-29 07:48:24 +00:00
|
|
|
/// Was the ABI in human readable format?
|
|
|
|
human_readable: bool,
|
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
/// The contract name as an identifier.
|
2022-02-24 20:09:08 +00:00
|
|
|
contract_ident: Ident,
|
|
|
|
|
|
|
|
/// The contract name as string
|
|
|
|
contract_name: String,
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
/// Manually specified method aliases.
|
2021-12-05 20:36:49 +00:00
|
|
|
method_aliases: BTreeMap<String, MethodAlias>,
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2022-08-02 18:03:52 +00:00
|
|
|
/// Manually specified method aliases.
|
|
|
|
error_aliases: BTreeMap<String, Ident>,
|
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
/// Derives added to event structs and enums.
|
|
|
|
event_derives: Vec<Path>,
|
2021-09-03 15:57:40 +00:00
|
|
|
|
|
|
|
/// Manually specified event aliases.
|
|
|
|
event_aliases: BTreeMap<String, Ident>,
|
2022-03-19 04:23:33 +00:00
|
|
|
|
|
|
|
/// Bytecode extracted from the abi string input, if present.
|
|
|
|
contract_bytecode: Option<Bytes>,
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
2021-10-11 14:18:09 +00:00
|
|
|
/// Expands the whole rust contract
|
|
|
|
pub fn expand(&self) -> Result<ExpandedContract> {
|
2022-02-24 20:09:08 +00:00
|
|
|
let name = &self.contract_ident;
|
2022-07-24 01:18:24 +00:00
|
|
|
let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
|
2022-03-19 04:23:33 +00:00
|
|
|
let abi_name = self.inline_abi_ident();
|
2020-06-02 21:10:46 +00:00
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
// 0. Imports
|
2020-06-22 08:44:08 +00:00
|
|
|
let imports = common::imports(&name.to_string());
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
// 1. Declare Contract struct
|
2022-03-19 04:23:33 +00:00
|
|
|
let struct_decl = common::struct_declaration(self);
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
// 2. Declare events structs & impl FromTokens for each event
|
2021-10-11 14:18:09 +00:00
|
|
|
let events_decl = self.events_declaration()?;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
// 3. impl block for the event functions
|
2021-10-11 14:18:09 +00:00
|
|
|
let contract_events = self.event_methods()?;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2021-10-18 10:28:38 +00:00
|
|
|
// 4. impl block for the contract methods and their corresponding types
|
|
|
|
let (contract_methods, call_structs) = self.methods_and_call_structs()?;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2022-03-19 04:23:33 +00:00
|
|
|
// 5. generate deploy function if
|
|
|
|
let deployment_methods = self.deployment_methods();
|
|
|
|
|
|
|
|
// 6. Declare the structs parsed from the human readable abi
|
2021-10-11 14:18:09 +00:00
|
|
|
let abi_structs_decl = self.abi_structs()?;
|
2021-03-16 19:37:19 +00:00
|
|
|
|
2022-08-02 18:03:52 +00:00
|
|
|
// 7. declare all error types
|
|
|
|
let errors_decl = self.errors()?;
|
|
|
|
|
2021-11-05 13:00:01 +00:00
|
|
|
let ethers_core = ethers_core_crate();
|
|
|
|
let ethers_contract = ethers_contract_crate();
|
|
|
|
let ethers_providers = ethers_providers_crate();
|
2021-08-28 18:53:01 +00:00
|
|
|
|
2021-10-11 14:18:09 +00:00
|
|
|
let contract = quote! {
|
2020-05-26 18:57:59 +00:00
|
|
|
#struct_decl
|
|
|
|
|
2022-05-06 15:15:49 +00:00
|
|
|
impl<M: #ethers_providers::Middleware> #name<M> {
|
2020-05-26 18:57:59 +00:00
|
|
|
/// Creates a new contract instance with the specified `ethers`
|
|
|
|
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
|
|
|
/// object
|
2021-08-28 18:53:01 +00:00
|
|
|
pub fn new<T: Into<#ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self {
|
2022-03-19 04:23:33 +00:00
|
|
|
#ethers_contract::Contract::new(address.into(), #abi_name.clone(), client).into()
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 04:23:33 +00:00
|
|
|
#deployment_methods
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
#contract_methods
|
|
|
|
|
|
|
|
#contract_events
|
2022-08-02 18:03:52 +00:00
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
2022-03-19 04:23:33 +00:00
|
|
|
|
|
|
|
impl<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
|
|
|
|
fn from(contract: #ethers_contract::Contract<M>) -> Self {
|
|
|
|
Self(contract)
|
|
|
|
}
|
|
|
|
}
|
2021-10-11 14:18:09 +00:00
|
|
|
};
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2021-10-11 14:18:09 +00:00
|
|
|
Ok(ExpandedContract {
|
|
|
|
module: name_mod,
|
|
|
|
imports,
|
|
|
|
contract,
|
|
|
|
events: events_decl,
|
2022-08-02 18:03:52 +00:00
|
|
|
errors: errors_decl,
|
2021-10-18 10:28:38 +00:00
|
|
|
call_structs,
|
2021-10-11 14:18:09 +00:00
|
|
|
abi_structs: abi_structs_decl,
|
2020-05-26 18:57:59 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a context from the code generation arguments.
|
2021-10-11 14:18:09 +00:00
|
|
|
pub fn from_abigen(args: Abigen) -> Result<Self> {
|
2020-05-26 18:57:59 +00:00
|
|
|
// get the actual ABI string
|
2022-01-08 09:17:36 +00:00
|
|
|
let mut abi_str =
|
2022-02-02 20:44:53 +00:00
|
|
|
args.abi_source.get().map_err(|e| eyre!("failed to get ABI JSON: {}", e))?;
|
2021-10-02 17:16:55 +00:00
|
|
|
|
2022-03-19 04:23:33 +00:00
|
|
|
// holds the bytecode parsed from the abi_str, if present
|
|
|
|
let mut contract_bytecode = None;
|
|
|
|
|
2022-08-24 00:54:56 +00:00
|
|
|
let (abi, human_readable, abi_parser) = parse_abi(&abi_str).wrap_err_with(|| {
|
|
|
|
eyre::eyre!("error parsing abi for contract: {}", args.contract_name)
|
|
|
|
})?;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2021-08-16 07:29:44 +00:00
|
|
|
// try to extract all the solidity structs from the normal JSON ABI
|
2021-10-29 12:29:35 +00:00
|
|
|
// we need to parse the json abi again because we need the internalType fields which are
|
|
|
|
// omitted by ethabi. If the ABI was defined as human readable we use the `internal_structs`
|
|
|
|
// from the Abi Parser
|
2021-10-02 14:34:01 +00:00
|
|
|
let internal_structs = if human_readable {
|
|
|
|
let mut internal_structs = InternalStructs::default();
|
2021-10-29 12:29:35 +00:00
|
|
|
// the types in the abi_parser are already valid rust types so simply clone them to make
|
|
|
|
// it consistent with the `RawAbi` variant
|
|
|
|
internal_structs
|
|
|
|
.rust_type_names
|
|
|
|
.extend(abi_parser.function_params.values().map(|ty| (ty.clone(), ty.clone())));
|
2021-10-02 14:34:01 +00:00
|
|
|
internal_structs.function_params = abi_parser.function_params.clone();
|
2022-09-07 16:14:13 +00:00
|
|
|
internal_structs.event_params = abi_parser.event_params.clone();
|
2021-10-02 14:34:01 +00:00
|
|
|
internal_structs.outputs = abi_parser.outputs.clone();
|
|
|
|
|
|
|
|
internal_structs
|
2022-03-19 04:23:33 +00:00
|
|
|
} else {
|
|
|
|
match serde_json::from_str::<JsonAbi>(&abi_str)? {
|
|
|
|
JsonAbi::Object(obj) => {
|
2022-02-23 10:46:52 +00:00
|
|
|
// need to update the `abi_str` here because we only want the `"abi": [...]`
|
|
|
|
// part of the json object in the contract binding
|
2022-03-19 04:23:33 +00:00
|
|
|
abi_str = serde_json::to_string(&obj.abi)?;
|
|
|
|
contract_bytecode = obj.bytecode;
|
|
|
|
InternalStructs::new(obj.abi)
|
2022-02-23 10:46:52 +00:00
|
|
|
}
|
2022-03-19 04:23:33 +00:00
|
|
|
JsonAbi::Array(abi) => InternalStructs::new(abi),
|
2022-02-23 10:46:52 +00:00
|
|
|
}
|
2021-10-02 14:34:01 +00:00
|
|
|
};
|
2021-08-16 07:29:44 +00:00
|
|
|
|
2022-02-24 20:09:08 +00:00
|
|
|
let contract_ident = util::ident(&args.contract_name);
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
// NOTE: We only check for duplicate signatures here, since if there are
|
|
|
|
// duplicate aliases, the compiler will produce a warning because a
|
|
|
|
// method will be re-defined.
|
2021-02-19 06:34:56 +00:00
|
|
|
let mut method_aliases = BTreeMap::new();
|
2020-05-26 18:57:59 +00:00
|
|
|
for (signature, alias) in args.method_aliases.into_iter() {
|
2021-12-05 20:36:49 +00:00
|
|
|
let alias = MethodAlias {
|
|
|
|
function_name: util::safe_ident(&alias),
|
|
|
|
struct_name: util::safe_pascal_case_ident(&alias),
|
|
|
|
};
|
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
if method_aliases.insert(signature.clone(), alias).is_some() {
|
2022-02-02 20:44:53 +00:00
|
|
|
eyre::bail!("duplicate method signature '{}' in method aliases", signature)
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-03 15:57:40 +00:00
|
|
|
let mut event_aliases = BTreeMap::new();
|
|
|
|
for (signature, alias) in args.event_aliases.into_iter() {
|
|
|
|
let alias = syn::parse_str(&alias)?;
|
|
|
|
event_aliases.insert(signature, alias);
|
|
|
|
}
|
|
|
|
|
2022-08-02 18:03:52 +00:00
|
|
|
// also check for overloaded events not covered by aliases, in which case we simply
|
2022-05-07 16:31:53 +00:00
|
|
|
// numerate them
|
|
|
|
for events in abi.events.values() {
|
2022-08-02 18:03:52 +00:00
|
|
|
insert_alias_names(
|
|
|
|
&mut event_aliases,
|
|
|
|
events.iter().map(|e| (e.abi_signature(), e.name.as_str())),
|
|
|
|
events::event_struct_alias,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut error_aliases = BTreeMap::new();
|
|
|
|
for (signature, alias) in args.error_aliases.into_iter() {
|
|
|
|
let alias = syn::parse_str(&alias)?;
|
|
|
|
error_aliases.insert(signature, alias);
|
|
|
|
}
|
|
|
|
|
|
|
|
// also check for overloaded errors not covered by aliases, in which case we simply
|
|
|
|
// numerate them
|
|
|
|
for errors in abi.errors.values() {
|
|
|
|
insert_alias_names(
|
|
|
|
&mut error_aliases,
|
|
|
|
errors.iter().map(|e| (e.abi_signature(), e.name.as_str())),
|
|
|
|
errors::error_struct_alias,
|
|
|
|
);
|
2022-05-07 16:31:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
let event_derives = args
|
|
|
|
.event_derives
|
|
|
|
.iter()
|
|
|
|
.map(|derive| syn::parse_str::<Path>(derive))
|
|
|
|
.collect::<Result<Vec<_>, _>>()
|
|
|
|
.context("failed to parse event derives")?;
|
|
|
|
|
|
|
|
Ok(Context {
|
|
|
|
abi,
|
2020-10-29 07:48:24 +00:00
|
|
|
human_readable,
|
2020-05-26 18:57:59 +00:00
|
|
|
abi_str: Literal::string(&abi_str),
|
2021-03-16 19:37:19 +00:00
|
|
|
abi_parser,
|
2021-08-16 07:29:44 +00:00
|
|
|
internal_structs,
|
2022-02-24 20:09:08 +00:00
|
|
|
contract_ident,
|
|
|
|
contract_name: args.contract_name,
|
2022-03-19 04:23:33 +00:00
|
|
|
contract_bytecode,
|
2020-05-26 18:57:59 +00:00
|
|
|
method_aliases,
|
2022-08-02 18:03:52 +00:00
|
|
|
error_aliases: Default::default(),
|
2020-05-26 18:57:59 +00:00
|
|
|
event_derives,
|
2021-09-03 15:57:40 +00:00
|
|
|
event_aliases,
|
2020-05-26 18:57:59 +00:00
|
|
|
})
|
|
|
|
}
|
2021-10-11 14:18:09 +00:00
|
|
|
|
2022-02-24 20:09:08 +00:00
|
|
|
/// The initial name fo the contract
|
|
|
|
pub(crate) fn contract_name(&self) -> &str {
|
|
|
|
&self.contract_name
|
|
|
|
}
|
|
|
|
|
2022-03-19 04:23:33 +00:00
|
|
|
/// 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()))
|
|
|
|
}
|
|
|
|
|
2021-10-11 14:18:09 +00:00
|
|
|
/// The internal abi struct mapping table
|
|
|
|
pub fn internal_structs(&self) -> &InternalStructs {
|
|
|
|
&self.internal_structs
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The internal mutable abi struct mapping table
|
|
|
|
pub fn internal_structs_mut(&mut self) -> &mut InternalStructs {
|
|
|
|
&mut self.internal_structs
|
|
|
|
}
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
2021-12-23 14:38:07 +00:00
|
|
|
|
2022-08-02 18:03:52 +00:00
|
|
|
/// Solidity supports overloading as long as the signature of an event, error, function is unique,
|
|
|
|
/// which results in a mapping `(name -> Vec<Element>)`
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// This will populate the alias map for the value in the mapping (`Vec<Element>`) via `abi
|
|
|
|
/// signature -> name` using the given aliases and merge it with all names not yet aliased.
|
|
|
|
///
|
|
|
|
/// If the iterator yields more than one element, this will simply numerate them
|
|
|
|
fn insert_alias_names<'a, I, F>(aliases: &mut BTreeMap<String, Ident>, elements: I, get_ident: F)
|
|
|
|
where
|
|
|
|
I: IntoIterator<Item = (String, &'a str)>,
|
|
|
|
F: Fn(&str) -> Ident,
|
|
|
|
{
|
|
|
|
let not_aliased =
|
|
|
|
elements.into_iter().filter(|(sig, _name)| !aliases.contains_key(sig)).collect::<Vec<_>>();
|
|
|
|
if not_aliased.len() > 1 {
|
|
|
|
let mut overloaded_aliases = Vec::new();
|
|
|
|
for (idx, (sig, name)) in not_aliased.into_iter().enumerate() {
|
|
|
|
let unique_name = format!("{}{}", name, idx + 1);
|
|
|
|
overloaded_aliases.push((sig, get_ident(&unique_name)));
|
|
|
|
}
|
|
|
|
aliases.extend(overloaded_aliases);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 14:38:07 +00:00
|
|
|
/// Parse the abi via `Source::parse` and return if the abi defined as human readable
|
|
|
|
fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> {
|
|
|
|
let mut abi_parser = AbiParser::default();
|
|
|
|
let res = if let Ok(abi) = abi_parser.parse_str(abi_str) {
|
|
|
|
(abi, true, abi_parser)
|
|
|
|
} else {
|
|
|
|
// a best-effort coercion of an ABI or an artifact JSON into an artifact JSON.
|
2022-08-24 00:54:56 +00:00
|
|
|
let contract: JsonContract = serde_json::from_str(abi_str)
|
|
|
|
.wrap_err_with(|| eyre::eyre!("failed deserializing abi:\n{}", abi_str))?;
|
2021-12-23 14:38:07 +00:00
|
|
|
|
2022-03-19 04:23:33 +00:00
|
|
|
(contract.into_abi(), false, abi_parser)
|
2021-12-23 14:38:07 +00:00
|
|
|
};
|
|
|
|
Ok(res)
|
|
|
|
}
|
2022-03-19 04:23:33 +00:00
|
|
|
|
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|