From 966fbde30242fbb288572896c27e601496529c83 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 17 Jul 2025 20:10:29 -0700 Subject: [PATCH 1/3] Add support to ABI for the `calls` field in 1.3 --- src/chain/abi.ts | 35 +- test/api.ts | 1 + ...4cf2c141822d1b9d92ee77affc01f4dad75f0.json | 79 +++++ test/serializer.ts | 324 +++++++++++++++++- 4 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 test/data/6894cf2c141822d1b9d92ee77affc01f4dad75f0.json diff --git a/src/chain/abi.ts b/src/chain/abi.ts index ff8e8ca9..0c985f13 100644 --- a/src/chain/abi.ts +++ b/src/chain/abi.ts @@ -3,7 +3,7 @@ import type {ABISerializableObject} from '../serializer/serializable' import {abiDecode, ABIDecoder} from '../serializer/decoder' import {abiEncode, ABIEncoder} from '../serializer/encoder' -import {Blob, Name, NameType} from '../' +import {Blob, Name, NameType, UInt64, UInt64Type} from '../' export type ABIDef = string | Partial | ABI | Blob @@ -26,6 +26,8 @@ export class ABI implements ABISerializableObject { ricardian_clauses: ABI.Clause[] /// Action Results action_results: ABI.ActionResult[] + /// Sync Calls + calls: ABI.Call[] constructor(args: Partial) { this.version = args.version || ABI.version @@ -36,6 +38,7 @@ export class ABI implements ABISerializableObject { this.tables = args.tables || [] this.ricardian_clauses = args.ricardian_clauses || [] this.action_results = args.action_results || [] + this.calls = args.calls || [] } static from(value: ABIDef) { @@ -141,6 +144,17 @@ export class ABI implements ABISerializableObject { action_results.push({name, result_type}) } } + const calls: ABI.Call[] = [] + if (decoder.canRead()) { + const numCalls = decoder.readVaruint32() + for (let i = 0; i < numCalls; i++) { + const name = decoder.readString() + const type = decoder.readString() + const id = UInt64.fromABI(decoder) + const result_type = decoder.readString() + calls.push({name, type, id, result_type}) + } + } return new ABI({ version, types, @@ -150,6 +164,7 @@ export class ABI implements ABISerializableObject { ricardian_clauses, variants, action_results, + calls, }) } @@ -210,6 +225,13 @@ export class ABI implements ABISerializableObject { Name.from(result.name).toABI(encoder) encoder.writeString(result.result_type) } + encoder.writeVaruint32(this.calls.length) + for (const call of this.calls) { + encoder.writeString(call.name) + encoder.writeString(call.type) + UInt64.from(String(call.id)).toABI(encoder) + encoder.writeString(call.result_type) + } } resolveType(name: string): ABI.ResolvedType { @@ -291,7 +313,8 @@ export class ABI implements ABISerializableObject { this.tables.length != o.tables.length || this.ricardian_clauses.length != o.ricardian_clauses.length || this.variants.length != o.variants.length || - this.action_results.length != o.action_results.length + this.action_results.length != o.action_results.length || + this.calls.length != o.calls.length ) { return false } @@ -310,6 +333,7 @@ export class ABI implements ABISerializableObject { abi_extensions: [], variants: this.variants, action_results: this.action_results, + calls: this.calls, } } } @@ -357,11 +381,18 @@ export namespace ABI { tables: Table[] ricardian_clauses: Clause[] action_results: ActionResult[] + calls: Call[] } export interface ActionResult { name: NameType result_type: string } + export interface Call { + name: string + type: string + id: UInt64Type + result_type: string + } export class ResolvedType { name: string id: number diff --git a/test/api.ts b/test/api.ts index 84f4904f..62d407c8 100644 --- a/test/api.ts +++ b/test/api.ts @@ -848,6 +848,7 @@ suite('api v1', function () { result_type: 'Result', }, ], + calls: [], } const info = await jungle4.v1.chain.get_info() const header = info.getTransactionHeader() diff --git a/test/data/6894cf2c141822d1b9d92ee77affc01f4dad75f0.json b/test/data/6894cf2c141822d1b9d92ee77affc01f4dad75f0.json new file mode 100644 index 00000000..3ae7116d --- /dev/null +++ b/test/data/6894cf2c141822d1b9d92ee77affc01f4dad75f0.json @@ -0,0 +1,79 @@ +{ + "api": "https://jungle4.api.eosnation.io", + "headers": { + "access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "connection": "close", + "content-length": "1683", + "content-type": "application/json", + "date": "Fri, 06 Oct 2023 18:23:19 GMT", + "server": "nginx/1.24.0" + }, + "status": 202, + "json": { + "transaction_id": "ac0b580f76914b330294081efca07e320777ae4227261f134fe39d38a7d5536f", + "processed": { + "id": "ac0b580f76914b330294081efca07e320777ae4227261f134fe39d38a7d5536f", + "block_num": 101720398, + "block_time": "2023-10-06T18:23:20.000", + "producer_block_id": null, + "receipt": { + "status": "executed", + "cpu_usage_us": 188, + "net_usage_words": 22 + }, + "elapsed": 188, + "net_usage": 176, + "scheduled": false, + "action_traces": [ + { + "action_ordinal": 1, + "creator_action_ordinal": 0, + "closest_unnotified_ancestor_action_ordinal": 0, + "receipt": { + "receiver": "eosio", + "act_digest": "756984726b93751e921d4d50e0a7ad53e6a56db8f4b0ed617096d712d504336b", + "global_sequence": 134833924, + "recv_sequence": 101790722, + "auth_sequence": [["corecorecore", 1179]], + "code_sequence": 4, + "abi_sequence": 4 + }, + "receiver": "eosio", + "act": { + "account": "eosio", + "name": "setabi", + "authorization": [ + { + "actor": "corecorecore", + "permission": "active" + } + ], + "data": { + "account": "corecorecore", + "abi": "0e656f73696f3a3a6162692f312e32000206526573756c7400010269640675696e743332047465737400010b656f735f6163636f756e74046e616d6501000000000090b1ca047465737400000000000001000000000090b1ca06526573756c74" + }, + "hex_data": "a02e45ea52a42e45600e656f73696f3a3a6162692f312e32000206526573756c7400010269640675696e743332047465737400010b656f735f6163636f756e74046e616d6501000000000090b1ca047465737400000000000001000000000090b1ca06526573756c74" + }, + "context_free": false, + "elapsed": 63, + "console": "", + "trx_id": "ac0b580f76914b330294081efca07e320777ae4227261f134fe39d38a7d5536f", + "block_num": 101720398, + "block_time": "2023-10-06T18:23:20.000", + "producer_block_id": null, + "account_ram_deltas": [], + "except": null, + "error_code": null, + "return_value_hex_data": "", + "inline_traces": [] + } + ], + "account_ram_delta": null, + "except": null, + "error_code": null + } + }, + "text": "{\"transaction_id\":\"ac0b580f76914b330294081efca07e320777ae4227261f134fe39d38a7d5536f\",\"processed\":{\"id\":\"ac0b580f76914b330294081efca07e320777ae4227261f134fe39d38a7d5536f\",\"block_num\":101720398,\"block_time\":\"2023-10-06T18:23:20.000\",\"producer_block_id\":null,\"receipt\":{\"status\":\"executed\",\"cpu_usage_us\":188,\"net_usage_words\":22},\"elapsed\":188,\"net_usage\":176,\"scheduled\":false,\"action_traces\":[{\"action_ordinal\":1,\"creator_action_ordinal\":0,\"closest_unnotified_ancestor_action_ordinal\":0,\"receipt\":{\"receiver\":\"eosio\",\"act_digest\":\"756984726b93751e921d4d50e0a7ad53e6a56db8f4b0ed617096d712d504336b\",\"global_sequence\":134833924,\"recv_sequence\":101790722,\"auth_sequence\":[[\"corecorecore\",1179]],\"code_sequence\":4,\"abi_sequence\":4},\"receiver\":\"eosio\",\"act\":{\"account\":\"eosio\",\"name\":\"setabi\",\"authorization\":[{\"actor\":\"corecorecore\",\"permission\":\"active\"}],\"data\":{\"account\":\"corecorecore\",\"abi\":\"0e656f73696f3a3a6162692f312e32000206526573756c7400010269640675696e743332047465737400010b656f735f6163636f756e74046e616d6501000000000090b1ca047465737400000000000001000000000090b1ca06526573756c74\"},\"hex_data\":\"a02e45ea52a42e45600e656f73696f3a3a6162692f312e32000206526573756c7400010269640675696e743332047465737400010b656f735f6163636f756e74046e616d6501000000000090b1ca047465737400000000000001000000000090b1ca06526573756c74\"},\"context_free\":false,\"elapsed\":63,\"console\":\"\",\"trx_id\":\"ac0b580f76914b330294081efca07e320777ae4227261f134fe39d38a7d5536f\",\"block_num\":101720398,\"block_time\":\"2023-10-06T18:23:20.000\",\"producer_block_id\":null,\"account_ram_deltas\":[],\"except\":null,\"error_code\":null,\"return_value_hex_data\":\"\",\"inline_traces\":[]}],\"account_ram_delta\":null,\"except\":null,\"error_code\":null}}" +} diff --git a/test/serializer.ts b/test/serializer.ts index 0f78e824..ef5938c0 100644 --- a/test/serializer.ts +++ b/test/serializer.ts @@ -668,6 +668,7 @@ suite('serializer', function () { tables: [], ricardian_clauses: [], action_results: [], + calls: [], }) }) @@ -1086,7 +1087,7 @@ suite('serializer', function () { const data = Serializer.encode({object: abi}) assert.equal( data.hexString, - '0e656f73696f3a3a6162692f312e310101620161010161000101660161000100000000000000c80369363401016b010369363401610103666f6f036261720000010176020161016200' + '0e656f73696f3a3a6162692f312e310101620161010161000101660161000100000000000000c80369363401016b010369363401610103666f6f03626172000001017602016101620000' ) const decoded = Serializer.objectify(Serializer.decode({data, type: ABI})) assert.deepEqual(abi.types, decoded.types) @@ -1274,4 +1275,325 @@ suite('serializer', function () { assert.isTrue(Name.from(decoded.action_results[0].name).equals('test')) assert.equal(decoded.action_results[0].result_type, 'Result') }) + + test('calls', function () { + const raw = { + ____comment: 'This file was generated with eosio-abigen. DO NOT EDIT ', + version: 'eosio::abi/1.3', + types: [], + structs: [ + { + name: 'Account', + base: '', + fields: [ + { + name: 'owner', + type: 'name', + }, + { + name: 'balance', + type: 'asset', + }, + ], + }, + { + name: 'Allowance', + base: '', + fields: [ + { + name: 'owner', + type: 'name', + }, + { + name: 'spender', + type: 'name', + }, + { + name: 'quantity', + type: 'asset', + }, + { + name: 'key', + type: 'uint128', + }, + ], + }, + { + name: 'Token', + base: '', + fields: [ + { + name: 'supply', + type: 'asset', + }, + { + name: 'name', + type: 'string', + }, + ], + }, + { + name: 'allowance', + base: '', + fields: [ + { + name: 'owner', + type: 'name', + }, + { + name: 'spender', + type: 'name', + }, + ], + }, + { + name: 'balanceof', + base: '', + fields: [ + { + name: 'account', + type: 'name', + }, + ], + }, + { + name: 'getname', + base: '', + fields: [], + }, + { + name: 'getsymbol', + base: '', + fields: [], + }, + { + name: 'init', + base: '', + fields: [ + { + name: 'supply', + type: 'asset', + }, + { + name: 'name', + type: 'string', + }, + ], + }, + { + name: 'setallowance', + base: '', + fields: [ + { + name: 'owner', + type: 'name', + }, + { + name: 'spender', + type: 'name', + }, + { + name: 'quantity', + type: 'asset', + }, + ], + }, + { + name: 'totalsupply', + base: '', + fields: [ + { + name: 'account', + type: 'name', + }, + ], + }, + { + name: 'transfer', + base: '', + fields: [ + { + name: 'from', + type: 'name', + }, + { + name: 'to', + type: 'name', + }, + { + name: 'quantity', + type: 'asset', + }, + { + name: 'memo', + type: 'string', + }, + ], + }, + { + name: 'transferfrom', + base: '', + fields: [ + { + name: 'from', + type: 'name', + }, + { + name: 'to', + type: 'name', + }, + { + name: 'quantity', + type: 'asset', + }, + { + name: 'memo', + type: 'string', + }, + ], + }, + ], + actions: [ + { + name: 'allowance', + type: 'allowance', + ricardian_contract: '', + }, + { + name: 'balanceof', + type: 'balanceof', + ricardian_contract: '', + }, + { + name: 'getname', + type: 'getname', + ricardian_contract: '', + }, + { + name: 'getsymbol', + type: 'getsymbol', + ricardian_contract: '', + }, + { + name: 'init', + type: 'init', + ricardian_contract: '', + }, + { + name: 'setallowance', + type: 'setallowance', + ricardian_contract: '', + }, + { + name: 'totalsupply', + type: 'totalsupply', + ricardian_contract: '', + }, + { + name: 'transfer', + type: 'transfer', + ricardian_contract: '', + }, + { + name: 'transferfrom', + type: 'transferfrom', + ricardian_contract: '', + }, + ], + tables: [ + { + name: 'accounts', + type: 'Account', + index_type: 'i64', + key_names: [], + key_types: [], + }, + { + name: 'allowances', + type: 'Allowance', + index_type: 'i64', + key_names: [], + key_types: [], + }, + { + name: 'token', + type: 'Token', + index_type: 'i64', + key_names: [], + key_types: [], + }, + ], + ricardian_clauses: [], + variants: [], + action_results: [ + { + name: 'allowance', + result_type: 'asset', + }, + { + name: 'balanceof', + result_type: 'asset', + }, + { + name: 'getname', + result_type: 'string', + }, + { + name: 'getsymbol', + result_type: 'symbol', + }, + { + name: 'totalsupply', + result_type: 'asset', + }, + ], + calls: [ + { + name: 'allowance', + type: 'allowance', + id: 249881365962022907, + result_type: 'asset', + }, + { + name: 'balanceof', + type: 'balanceof', + id: 249882303009262080, + result_type: 'asset', + }, + { + name: 'getname', + type: 'getname', + id: 229466957163974, + result_type: 'string', + }, + { + name: 'getsymbol', + type: 'getsymbol', + id: 249889516575707259, + result_type: 'symbol', + }, + { + name: 'totalsupply', + type: 'totalsupply', + id: 13895640377000271446, + result_type: 'asset', + }, + { + name: 'transferfrom', + type: 'transferfrom', + id: 15838006154959782046, + result_type: '', + }, + ], + } + const abi = ABI.from(raw) + const encoded = Serializer.encode({object: abi}) + const decoded = Serializer.decode({data: encoded, type: ABI}) + assert.isTrue(abi.equals(decoded)) + assert.lengthOf(decoded.calls, 6) + assert.equal(decoded.calls[0].name, 'allowance') + assert.equal(decoded.calls[0].type, 'allowance') + assert.equal(decoded.calls[0].id, 249881365962022907) + assert.equal(decoded.calls[0].result_type, 'asset') + }) }) From d799b0dd2ef5ef693576051e99c5c2f6420ac412 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 17 Jul 2025 20:11:17 -0700 Subject: [PATCH 2/3] v1.2.0-rc1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b088f16..81815a19 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/antelope", "description": "Library for working with Antelope powered blockchains.", - "version": "1.1.0", + "version": "1.2.0-rc1", "homepage": "https://github.com/wharfkit/antelope", "license": "BSD-3-Clause-No-Military-License", "main": "lib/antelope.js", From 1d9066a1ecb1a132ec57615b544fa2c5eee122c7 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Jul 2025 12:43:17 -0700 Subject: [PATCH 3/3] Ensure action exists on the provided ABI --- src/chain/action.ts | 5 +++++ test/chain.ts | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/chain/action.ts b/src/chain/action.ts index c0b7e825..9662d98b 100644 --- a/src/chain/action.ts +++ b/src/chain/action.ts @@ -64,6 +64,11 @@ export class Action extends Struct { let type: string | undefined if (abi) { type = ABI.from(abi).getActionType(object.name) + if (!type) { + throw new Error( + `The action "${object.name}" does not exist on the ABI provided.` + ) + } } else if (!data.constructor || data.constructor.abiName === undefined) { throw new Error( 'Missing ABI definition when creating action with untyped action data' diff --git a/test/chain.ts b/test/chain.ts index 30308dcc..cd063dc6 100644 --- a/test/chain.ts +++ b/test/chain.ts @@ -865,4 +865,18 @@ suite('chain', function () { Serializer.encode({object: {input: [1, 2, 3, 4]}, abi, type: 'fixed'}).hexString ) }) + + test('action does not exist in ABI', function () { + assert.throws(() => + Action.from( + { + account: 'foo', + name: 'bar', + authorization: [{actor: 'foo', permission: 'bar'}], + data: {}, + }, + {} + ) + ) + }) })