From a079dcc12625b9875af320b6df2dde1e9c0b803d Mon Sep 17 00:00:00 2001 From: YaroShkvorets Date: Fri, 4 Aug 2023 13:29:29 -0400 Subject: [PATCH] add wrapped abi support ref #18 --- Cargo.toml | 2 +- abigen-tests/abi/bounties.rs.golden | 5 +- abigen-tests/abi/grants.rs.golden | 5 +- abigen-tests/abi/token.rs.golden | 5 +- abigen-tests/abi/token_wrapped.json | 144 +++++++++++++++++++++++ abigen-tests/abi/token_wrapped.rs.golden | 111 +++++++++++++++++ abigen/src/abi.rs | 21 ++++ abigen/src/assert.rs | 2 +- abigen/src/contract.rs | 48 +++++++- abigen/src/lib.rs | 10 +- 10 files changed, 341 insertions(+), 12 deletions(-) create mode 100644 abigen-tests/abi/token_wrapped.json create mode 100644 abigen-tests/abi/token_wrapped.rs.golden diff --git a/Cargo.toml b/Cargo.toml index 2eb3e88..481418c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ opt-level = 's' strip = "debuginfo" [workspace.package] -version = "0.3.1" +version = "0.3.2" edition = "2021" description = "Substreams development kit for Antelope chains, contains Firehose Block model and helpers." authors = ["Fred ", "Denis ", "Yaro "] diff --git a/abigen-tests/abi/bounties.rs.golden b/abigen-tests/abi/bounties.rs.golden index cb03d37..0ea4e81 100644 --- a/abigen-tests/abi/bounties.rs.golden +++ b/abigen-tests/abi/bounties.rs.golden @@ -1,3 +1,5 @@ +#[allow(dead_code)] +pub const ACCOUNT: Option<&'static str> = None; pub mod types { use substreams_antelope::types::*; #[derive(Debug, Clone, PartialEq, serde::Deserialize)] @@ -84,8 +86,9 @@ pub mod types { } pub mod actions { use substreams_antelope::types::*; - use super::types::*; use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; #[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Apply { diff --git a/abigen-tests/abi/grants.rs.golden b/abigen-tests/abi/grants.rs.golden index be3c7ae..c6b5ff3 100644 --- a/abigen-tests/abi/grants.rs.golden +++ b/abigen-tests/abi/grants.rs.golden @@ -1,3 +1,5 @@ +#[allow(dead_code)] +pub const ACCOUNT: Option<&'static str> = None; pub mod types { use substreams_antelope::types::*; #[derive(Debug, Clone, PartialEq, serde::Deserialize)] @@ -122,8 +124,9 @@ pub mod types { } pub mod actions { use substreams_antelope::types::*; - use super::types::*; use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; #[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Cleartable { diff --git a/abigen-tests/abi/token.rs.golden b/abigen-tests/abi/token.rs.golden index b23055f..0cc570d 100644 --- a/abigen-tests/abi/token.rs.golden +++ b/abigen-tests/abi/token.rs.golden @@ -1,3 +1,5 @@ +#[allow(dead_code)] +pub const ACCOUNT: Option<&'static str> = None; pub mod types { use substreams_antelope::types::*; #[derive(Debug, Clone, PartialEq, serde::Deserialize)] @@ -15,8 +17,9 @@ pub mod types { } pub mod actions { use substreams_antelope::types::*; - use super::types::*; use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; #[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Close { diff --git a/abigen-tests/abi/token_wrapped.json b/abigen-tests/abi/token_wrapped.json new file mode 100644 index 0000000..c746456 --- /dev/null +++ b/abigen-tests/abi/token_wrapped.json @@ -0,0 +1,144 @@ +{ + "account_name": "eosio.token", + "abi": { + "version": "eosio::abi/1.1", + "types": [], + "structs": [{ + "name": "account", + "base": "", + "fields": [{ + "name": "balance", + "type": "asset" + }] + }, { + "name": "close", + "base": "", + "fields": [{ + "name": "owner", + "type": "name" + }, { + "name": "symbol", + "type": "symbol" + }] + }, { + "name": "create", + "base": "", + "fields": [{ + "name": "issuer", + "type": "name" + }, { + "name": "maximum_supply", + "type": "asset" + }] + }, { + "name": "currency_stats", + "base": "", + "fields": [{ + "name": "supply", + "type": "asset" + }, { + "name": "max_supply", + "type": "asset" + }, { + "name": "issuer", + "type": "name" + }] + }, { + "name": "issue", + "base": "", + "fields": [{ + "name": "to", + "type": "name" + }, { + "name": "quantity", + "type": "asset" + }, { + "name": "memo", + "type": "string" + }] + }, { + "name": "open", + "base": "", + "fields": [{ + "name": "owner", + "type": "name" + }, { + "name": "symbol", + "type": "symbol" + }, { + "name": "ram_payer", + "type": "name" + }] + }, { + "name": "retire", + "base": "", + "fields": [{ + "name": "quantity", + "type": "asset" + }, { + "name": "memo", + "type": "string" + }] + }, { + "name": "transfer", + "base": "", + "fields": [{ + "name": "from", + "type": "name" + }, { + "name": "to", + "type": "name" + }, { + "name": "quantity", + "type": "asset" + }, { + "name": "memo", + "type": "string" + }] + }], + "actions": [{ + "name": "close", + "type": "close", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Close Token Balance\nsummary: 'Close {{nowrap owner}}’s zero quantity balance'\nicon: https://raw.githubusercontent.com/cryptokylin/eosio.contracts/v1.7.0/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{owner}} agrees to close their zero quantity balance for the {{symbol_to_symbol_code symbol}} token.\n\nRAM will be refunded to the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}." + }, { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create New Token\nsummary: 'Create a new token'\nicon: https://raw.githubusercontent.com/cryptokylin/eosio.contracts/v1.7.0/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{$action.account}} agrees to create a new token with symbol {{asset_to_symbol_code maximum_supply}} to be managed by {{issuer}}.\n\nThis action will not result any any tokens being issued into circulation.\n\n{{issuer}} will be allowed to issue tokens into circulation, up to a maximum supply of {{maximum_supply}}.\n\nRAM will deducted from {{$action.account}}’s resources to create the necessary records." + }, { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue Tokens into Circulation\nsummary: 'Issue {{nowrap quantity}} into circulation and transfer into {{nowrap to}}’s account'\nicon: https://raw.githubusercontent.com/cryptokylin/eosio.contracts/v1.7.0/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\nThe token manager agrees to issue {{quantity}} into circulation, and transfer it into {{to}}’s account.\n\n{{#if memo}}There is a memo attached to the transfer stating:\n{{memo}}\n{{/if}}\n\nIf {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, or the token manager does not have a balance for {{asset_to_symbol_code quantity}}, the token manager will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from the token manager’s resources to create the necessary records.\n\nThis action does not allow the total quantity to exceed the max allowed supply of the token." + }, { + "name": "open", + "type": "open", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Open Token Balance\nsummary: 'Open a zero quantity balance for {{nowrap owner}}'\nicon: https://raw.githubusercontent.com/cryptokylin/eosio.contracts/v1.7.0/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{ram_payer}} agrees to establish a zero quantity balance for {{owner}} for the {{symbol_to_symbol_code symbol}} token.\n\nIf {{owner}} does not have a balance for {{symbol_to_symbol_code symbol}}, {{ram_payer}} will be designated as the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}. As a result, RAM will be deducted from {{ram_payer}}’s resources to create the necessary records." + }, { + "name": "retire", + "type": "retire", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove Tokens from Circulation\nsummary: 'Remove {{nowrap quantity}} from circulation'\nicon: https://raw.githubusercontent.com/cryptokylin/eosio.contracts/v1.7.0/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\nThe token manager agrees to remove {{quantity}} from circulation, taken from their own account.\n\n{{#if memo}} There is a memo attached to the action stating:\n{{memo}}\n{{/if}}" + }, { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Send {{nowrap quantity}} from {{nowrap from}} to {{nowrap to}}'\nicon: https://raw.githubusercontent.com/cryptokylin/eosio.contracts/v1.7.0/contracts/icons/transfer.png#5dfad0df72772ee1ccc155e670c1d124f5c5122f1d5027565df38b418042d1dd\n---\n\n{{from}} agrees to send {{quantity}} to {{to}}.\n\n{{#if memo}}There is a memo attached to the transfer stating:\n{{memo}}\n{{/if}}\n\nIf {{from}} is not already the RAM payer of their {{asset_to_symbol_code quantity}} token balance, {{from}} will be designated as such. As a result, RAM will be deducted from {{from}}’s resources to refund the original RAM payer.\n\nIf {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, {{from}} will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from {{from}}’s resources to create the necessary records." + }], + "tables": [{ + "name": "accounts", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "account" + }, { + "name": "stat", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "currency_stats" + }], + "ricardian_clauses": [], + "error_messages": [], + "abi_extensions": [], + "variants": [], + "action_results": [], + "kv_tables": {} + } +} \ No newline at end of file diff --git a/abigen-tests/abi/token_wrapped.rs.golden b/abigen-tests/abi/token_wrapped.rs.golden new file mode 100644 index 0000000..8264d1c --- /dev/null +++ b/abigen-tests/abi/token_wrapped.rs.golden @@ -0,0 +1,111 @@ +#[allow(dead_code)] +pub const ACCOUNT: Option<&'static str> = Some("eosio.token"); +pub mod types { + use substreams_antelope::types::*; + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Account { + pub balance: Asset, + } + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct CurrencyStats { + pub supply: Asset, + pub max_supply: Asset, + pub issuer: Name, + } +} +pub mod actions { + use substreams_antelope::types::*; + use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Close { + pub owner: Name, + pub symbol: Symbol, + } + impl substreams_antelope::Action for Close { + const NAME: &'static str = "close"; + fn decode( + trace: &substreams_antelope::pb::ActionTrace, + ) -> Result { + Ok(decode::(&trace.action.as_ref().unwrap().json_data)?) + } + } + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Create { + pub issuer: Name, + pub maximum_supply: Asset, + } + impl substreams_antelope::Action for Create { + const NAME: &'static str = "create"; + fn decode( + trace: &substreams_antelope::pb::ActionTrace, + ) -> Result { + Ok(decode::(&trace.action.as_ref().unwrap().json_data)?) + } + } + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Issue { + pub to: Name, + pub quantity: Asset, + pub memo: String, + } + impl substreams_antelope::Action for Issue { + const NAME: &'static str = "issue"; + fn decode( + trace: &substreams_antelope::pb::ActionTrace, + ) -> Result { + Ok(decode::(&trace.action.as_ref().unwrap().json_data)?) + } + } + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Open { + pub owner: Name, + pub symbol: Symbol, + pub ram_payer: Name, + } + impl substreams_antelope::Action for Open { + const NAME: &'static str = "open"; + fn decode( + trace: &substreams_antelope::pb::ActionTrace, + ) -> Result { + Ok(decode::(&trace.action.as_ref().unwrap().json_data)?) + } + } + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Retire { + pub quantity: Asset, + pub memo: String, + } + impl substreams_antelope::Action for Retire { + const NAME: &'static str = "retire"; + fn decode( + trace: &substreams_antelope::pb::ActionTrace, + ) -> Result { + Ok(decode::(&trace.action.as_ref().unwrap().json_data)?) + } + } + #[derive(Debug, Clone, PartialEq, serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Transfer { + pub from: Name, + pub to: Name, + pub quantity: Asset, + pub memo: String, + } + impl substreams_antelope::Action for Transfer { + const NAME: &'static str = "transfer"; + fn decode( + trace: &substreams_antelope::pb::ActionTrace, + ) -> Result { + Ok(decode::(&trace.action.as_ref().unwrap().json_data)?) + } + } +} \ No newline at end of file diff --git a/abigen/src/abi.rs b/abigen/src/abi.rs index 48878e1..f3f5b8e 100644 --- a/abigen/src/abi.rs +++ b/abigen/src/abi.rs @@ -19,6 +19,7 @@ pub struct ABIAction { pub name: String, #[serde(rename = "type")] pub ty: String, + #[serde(default)] pub ricardian_contract: String, } @@ -28,7 +29,9 @@ pub struct ABITable { #[serde(rename = "type")] ty: String, index_type: String, + #[serde(default)] key_names: Vec, + #[serde(default)] key_types: Vec, } @@ -64,12 +67,30 @@ pub struct ABIErrorMessage { error_msg: String, } +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct WrappedABI { + pub account_name: String, + pub abi: ABI, +} + +impl TryFrom<&str> for WrappedABI { + type Error = serde_json::Error; + #[inline] + fn try_from(str: &str) -> Result { + serde_json::from_str(str) + } +} + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ABI { pub version: String, + #[serde(default)] pub types: Vec, + #[serde(default)] pub structs: Vec, + #[serde(default)] pub actions: Vec, + #[serde(default)] pub tables: Vec, #[serde(default)] pub variants: Vec, diff --git a/abigen/src/assert.rs b/abigen/src/assert.rs index a111053..d7cd43a 100644 --- a/abigen/src/assert.rs +++ b/abigen/src/assert.rs @@ -8,7 +8,7 @@ pub(crate) fn assert_ast_eq(actual: proc_macro2::TokenStream, expected: proc_mac #[cfg(test)] fn pretty_print_item(item: proc_macro2::TokenStream) -> String { - let file = syn::parse_file(&item.to_string()).unwrap(); + let file = syn::parse_file(&item.to_string()).expect("Failed to parse TokenStream file"); prettyplease::unparse(&file) } diff --git a/abigen/src/contract.rs b/abigen/src/contract.rs index cd85d9f..1fd0117 100644 --- a/abigen/src/contract.rs +++ b/abigen/src/contract.rs @@ -12,19 +12,27 @@ pub struct Contract { } impl Contract { - pub fn generate(&self) -> TokenStream { + pub fn generate(&self, account: Option) -> TokenStream { let types: Vec<_> = self.types.iter().filter(|ty| !ty.is_entity).map(Type::generate).collect(); let actions: Vec<_> = self.actions.iter().map(Action::generate).collect(); + let account = match account { + Some(value) => quote! { Some(#value) }, + None => quote! { None }, + }; + quote! { + #[allow(dead_code)] + pub const ACCOUNT: Option<&'static str> = #account; pub mod types { use substreams_antelope::types::*; #(#types)* } pub mod actions { use substreams_antelope::types::*; - use super::types::*; use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; #(#actions)* } } @@ -72,15 +80,18 @@ mod test { let c = Contract::from(abi_contract); assert_ast_eq( - c.generate(), + c.generate(None), quote! { + #[allow(dead_code)] + pub const ACCOUNT: Option<& 'static str> = None; pub mod types { use substreams_antelope::types::*; } pub mod actions { use substreams_antelope::types::*; - use super::types::*; use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; } }, ); @@ -93,15 +104,42 @@ mod test { let c = Contract::from(abi_contract); assert_ast_eq( - c.generate(), + c.generate(None), quote! { + #[allow(dead_code)] + pub const ACCOUNT: Option<& 'static str> = None; pub mod types { use substreams_antelope::types::*; } pub mod actions { use substreams_antelope::types::*; + use substreams_antelope::decoder::decode; + #[allow(unused_imports)] use super::types::*; + } + }, + ); + } + + #[test] + fn test_wrapped() { + let abi_contract = Contract { ..Default::default() }; + + let c = Contract::from(abi_contract); + + assert_ast_eq( + c.generate(Some("tokencontract".to_owned())), + quote! { + #[allow(dead_code)] + pub const ACCOUNT: Option<&'static str> = Some("tokencontract"); + pub mod types { + use substreams_antelope::types::*; + } + pub mod actions { + use substreams_antelope::types::*; use substreams_antelope::decoder::decode; + #[allow(unused_imports)] + use super::types::*; } }, ); diff --git a/abigen/src/lib.rs b/abigen/src/lib.rs index f7484c6..2a93f78 100644 --- a/abigen/src/lib.rs +++ b/abigen/src/lib.rs @@ -28,9 +28,15 @@ pub fn generate_abi_code>(path: S) -> Result (c, None), + Err(_) => { + let w = abi::WrappedABI::try_from(contents.as_str())?; + (w.abi, Some(w.account_name)) + } + }; let c = contract::Contract::from(contract); - Ok(c.generate()) + Ok(c.generate(account_name)) } pub fn normalize_path>(relative_path: S) -> Result {