From 1ed61ea74ea84730925de908abad12081b92fc9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 25 Oct 2023 23:00:15 +0300 Subject: [PATCH 01/13] Handle "events" in abi registry. --- src/smartcontracts/argSerializer.ts | 10 +- .../typesystem/abiRegistry.spec.ts | 16 + src/smartcontracts/typesystem/abiRegistry.ts | 32 + src/smartcontracts/typesystem/event.ts | 40 + src/testdata/esdt-safe.abi.json | 725 ++++++++++++++++++ 5 files changed, 820 insertions(+), 3 deletions(-) create mode 100644 src/smartcontracts/typesystem/event.ts create mode 100644 src/testdata/esdt-safe.abi.json diff --git a/src/smartcontracts/argSerializer.ts b/src/smartcontracts/argSerializer.ts index 164ed9dc..e9e8f214 100644 --- a/src/smartcontracts/argSerializer.ts +++ b/src/smartcontracts/argSerializer.ts @@ -1,6 +1,6 @@ import { ARGUMENTS_SEPARATOR } from "../constants"; import { BinaryCodec } from "./codec"; -import { EndpointParameterDefinition, Type, TypedValue, U32Type, U32Value } from "./typesystem"; +import { Type, TypedValue, U32Type, U32Value } from "./typesystem"; import { OptionalType, OptionalValue } from "./typesystem/algebraic"; import { CompositeType, CompositeValue } from "./typesystem/composite"; import { VariadicType, VariadicValue } from "./typesystem/variadic"; @@ -15,6 +15,10 @@ interface ICodec { encodeTopLevel(typedValue: TypedValue): Buffer; } +interface IParameterDefinition { + type: Type; +} + // TODO: perhaps move default construction options to a factory (ArgSerializerFactory), instead of referencing them in the constructor // (postpone as much as possible, breaking change) const defaultArgSerializerrOptions: IArgSerializerOptions = { @@ -32,7 +36,7 @@ export class ArgSerializer { /** * Reads typed values from an arguments string (e.g. aa@bb@@cc), given parameter definitions. */ - stringToValues(joinedString: string, parameters: EndpointParameterDefinition[]): TypedValue[] { + stringToValues(joinedString: string, parameters: IParameterDefinition[]): TypedValue[] { let buffers = this.stringToBuffers(joinedString); let values = this.buffersToValues(buffers, parameters); return values; @@ -49,7 +53,7 @@ export class ArgSerializer { /** * Decodes a set of buffers into a set of typed values, given parameter definitions. */ - buffersToValues(buffers: Buffer[], parameters: EndpointParameterDefinition[]): TypedValue[] { + buffersToValues(buffers: Buffer[], parameters: IParameterDefinition[]): TypedValue[] { // TODO: Refactor, split (function is quite complex). const self = this; diff --git a/src/smartcontracts/typesystem/abiRegistry.spec.ts b/src/smartcontracts/typesystem/abiRegistry.spec.ts index 1b8ccd85..55910ace 100644 --- a/src/smartcontracts/typesystem/abiRegistry.spec.ts +++ b/src/smartcontracts/typesystem/abiRegistry.spec.ts @@ -140,4 +140,20 @@ describe("test abi registry", () => { assert.deepEqual(registry.getEndpoint("bar").output[0].type, new VariadicType(new U32Type(), true)); assert.deepEqual(registry.getEndpoint("bar").output[1].type, new VariadicType(new BytesType(), true)); }); + + it("should load ABI wih events", async () => { + const registry = await loadAbiRegistry("src/testdata/esdt-safe.abi.json"); + + assert.lengthOf(registry.events, 8); + + const depositEvent = registry.getEvent("deposit"); + assert.deepEqual(depositEvent.inputs[0].type, new AddressType()); + assert.deepEqual(depositEvent.inputs[1].type, new ListType(registry.getCustomType("EsdtTokenPayment"))); + assert.deepEqual(depositEvent.inputs[2].type, registry.getCustomType("DepositEvent")); + + const setStatusEvent = registry.getEvent("setStatusEvent"); + assert.deepEqual(setStatusEvent.inputs[0].type, new U64Type()); + assert.deepEqual(setStatusEvent.inputs[1].type, new U64Type()); + assert.deepEqual(setStatusEvent.inputs[2].type, registry.getCustomType("TransactionStatus")); + }); }); diff --git a/src/smartcontracts/typesystem/abiRegistry.ts b/src/smartcontracts/typesystem/abiRegistry.ts index 2f4679a4..3ae018c5 100644 --- a/src/smartcontracts/typesystem/abiRegistry.ts +++ b/src/smartcontracts/typesystem/abiRegistry.ts @@ -2,6 +2,7 @@ import * as errors from "../../errors"; import { guardValueIsSetWithMessage } from "../../utils"; import { EndpointDefinition, EndpointParameterDefinition } from "./endpoint"; import { EnumType } from "./enum"; +import { EventDefinition, EventTopicDefinition } from "./event"; import { StructType } from "./struct"; import { TypeMapper } from "./typeMapper"; import { CustomType } from "./types"; @@ -13,17 +14,20 @@ export class AbiRegistry { readonly constructorDefinition: EndpointDefinition; readonly endpoints: EndpointDefinition[] = []; readonly customTypes: CustomType[] = []; + readonly events: EventDefinition[] = []; private constructor(options: { name: string; constructorDefinition: EndpointDefinition; endpoints: EndpointDefinition[]; customTypes: CustomType[], + events?: EventDefinition[] }) { this.name = options.name; this.constructorDefinition = options.constructorDefinition; this.endpoints = options.endpoints; this.customTypes = options.customTypes; + this.events = options.events || []; } static create(options: { @@ -31,11 +35,13 @@ export class AbiRegistry { constructor?: any, endpoints?: any[]; types?: Record + events?: any[] }): AbiRegistry { const name = options.name || interfaceNamePlaceholder; const constructor = options.constructor || {}; const endpoints = options.endpoints || []; const types = options.types || {}; + const events = options.events || []; // Load arbitrary input parameters into properly-defined objects (e.g. EndpointDefinition and CustomType). const constructorDefinition = EndpointDefinition.fromJSON({ name: "constructor", ...constructor }); @@ -54,17 +60,26 @@ export class AbiRegistry { } } + const eventDefinitions = events.map(item => EventDefinition.fromJSON(item)); + const registry = new AbiRegistry({ name: name, constructorDefinition: constructorDefinition, endpoints: endpointDefinitions, customTypes: customTypes, + events: eventDefinitions }); const remappedRegistry = registry.remapToKnownTypes(); return remappedRegistry; } + getCustomType(name: string): CustomType { + const result = this.customTypes.find((e) => e.getName() == name); + guardValueIsSetWithMessage(`custom type [${name}] not found`, result); + return result!; + } + getStruct(name: string): StructType { const result = this.customTypes.find((e) => e.getName() == name && e.hasExactClass(StructType.ClassName)); guardValueIsSetWithMessage(`struct [${name}] not found`, result); @@ -95,6 +110,12 @@ export class AbiRegistry { return result!; } + getEvent(name: string): EventDefinition { + const result = this.events.find((e) => e.identifier == name); + guardValueIsSetWithMessage(`event [${name}] not found`, result); + return result!; + } + /** * Right after loading ABI definitions into a registry, the endpoints and the custom types (structs, enums) * use raw types for their I/O parameters (in the case of endpoints), or for their fields (in the case of structs). @@ -129,12 +150,15 @@ export class AbiRegistry { newEndpoints.push(mapEndpoint(endpoint, mapper)); } + const newEvents: EventDefinition[] = this.events.map((event) => mapEvent(event, mapper)); + // Now return the new registry, with all types remapped to known types const newRegistry = new AbiRegistry({ name: this.name, constructorDefinition: newConstructor, endpoints: newEndpoints, customTypes: newCustomTypes, + events: newEvents }); return newRegistry; @@ -172,3 +196,11 @@ function mapEndpoint(endpoint: EndpointDefinition, mapper: TypeMapper): Endpoint return new EndpointDefinition(endpoint.name, newInput, newOutput, endpoint.modifiers); } + +function mapEvent(event: EventDefinition, mapper: TypeMapper): EventDefinition { + const newInputs = event.inputs.map( + (e) => new EventTopicDefinition(e.name, mapper.mapType(e.type)) + ); + + return new EventDefinition(event.identifier, newInputs); +} diff --git a/src/smartcontracts/typesystem/event.ts b/src/smartcontracts/typesystem/event.ts new file mode 100644 index 00000000..b1356431 --- /dev/null +++ b/src/smartcontracts/typesystem/event.ts @@ -0,0 +1,40 @@ +import { TypeExpressionParser } from "./typeExpressionParser"; +import { Type } from "./types"; + +const NamePlaceholder = "?"; + +export class EventDefinition { + readonly identifier: string; + readonly inputs: EventTopicDefinition[] = []; + + constructor(identifier: string, inputs: EventTopicDefinition[]) { + this.identifier = identifier; + this.inputs = inputs || []; + } + + static fromJSON(json: { + identifier: string, + inputs: any[] + }): EventDefinition { + json.identifier = json.identifier == null ? NamePlaceholder : json.identifier; + json.inputs = json.inputs || []; + + const inputs = json.inputs.map(param => EventTopicDefinition.fromJSON(param)); + return new EventDefinition(json.identifier, inputs); + } +} + +export class EventTopicDefinition { + readonly name: string; + readonly type: Type; + + constructor(name: string, type: Type) { + this.name = name; + this.type = type; + } + + static fromJSON(json: { name?: string, type: string }): EventTopicDefinition { + let parsedType = new TypeExpressionParser().parse(json.type); + return new EventTopicDefinition(json.name || NamePlaceholder, parsedType); + } +} diff --git a/src/testdata/esdt-safe.abi.json b/src/testdata/esdt-safe.abi.json new file mode 100644 index 00000000..ee17c3f3 --- /dev/null +++ b/src/testdata/esdt-safe.abi.json @@ -0,0 +1,725 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.75.0-nightly", + "commitHash": "42b1224e9eb37177f608d3f6a6f2be2ee13902e4", + "commitDate": "2023-10-15", + "channel": "Nightly", + "short": "rustc 1.75.0-nightly (42b1224e9 2023-10-15)" + }, + "contractCrate": { + "name": "esdt-safe", + "version": "0.0.0" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.43.5" + } + }, + "name": "EsdtSafe", + "constructor": { + "inputs": [ + { + "name": "min_valid_signers", + "type": "u32" + }, + { + "name": "signers", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "upgrade", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "docs": [ + "Create an Elrond -> Sovereign transaction." + ], + "name": "deposit", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "opt_transfer_data", + "type": "optional", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "docs": [ + "Claim funds for failed Elrond -> Sovereign transactions.", + "These are not sent automatically to prevent the contract getting stuck.", + "For example, if the receiver is a SC, a frozen account, etc." + ], + "name": "claimRefund", + "mutability": "mutable", + "inputs": [ + { + "name": "token_id", + "type": "TokenIdentifier" + } + ], + "outputs": [ + { + "type": "List" + } + ] + }, + { + "docs": [ + "Sets the statuses for the transactions, after they were executed on the Sovereign side.", + "", + "Only TransactionStatus::Executed (3) and TransactionStatus::Rejected (4) values are allowed.", + "Number of provided statuses must be equal to number of transactions in the batch." + ], + "name": "setTransactionBatchStatus", + "mutability": "mutable", + "inputs": [ + { + "name": "batch_id", + "type": "u64" + }, + { + "name": "signature", + "type": "array48" + }, + { + "name": "tx_statuses", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "setMinValidSigners", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "new_value", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "addSigners", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "signers", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "removeSigners", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "signers", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "registerToken", + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [ + { + "name": "sov_token_id", + "type": "TokenIdentifier" + }, + { + "name": "token_type", + "type": "EsdtTokenType" + }, + { + "name": "token_display_name", + "type": "bytes" + }, + { + "name": "token_ticker", + "type": "bytes" + }, + { + "name": "num_decimals", + "type": "u32" + }, + { + "name": "bls_multisig", + "type": "array48" + } + ], + "outputs": [] + }, + { + "name": "clearRegisteredToken", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "sov_token_id", + "type": "TokenIdentifier" + } + ], + "outputs": [] + }, + { + "name": "batchTransferEsdtToken", + "mutability": "mutable", + "inputs": [ + { + "name": "batch_id", + "type": "u64" + }, + { + "name": "signature", + "type": "array48" + }, + { + "name": "transfers", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "setMaxTxBatchSize", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "new_max_tx_batch_size", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "setMaxTxBatchBlockDuration", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "new_max_tx_batch_block_duration", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "getCurrentTxBatch", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "optional,List,Option>>>>", + "multi_result": true + } + ] + }, + { + "name": "getFirstBatchAnyStatus", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "optional,List,Option>>>>", + "multi_result": true + } + ] + }, + { + "name": "getBatch", + "mutability": "readonly", + "inputs": [ + { + "name": "batch_id", + "type": "u64" + } + ], + "outputs": [ + { + "type": "optional,List,Option>>>>", + "multi_result": true + } + ] + }, + { + "name": "getBatchStatus", + "mutability": "readonly", + "inputs": [ + { + "name": "batch_id", + "type": "u64" + } + ], + "outputs": [ + { + "type": "BatchStatus" + } + ] + }, + { + "name": "getFirstBatchId", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "getLastBatchId", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "setMaxBridgedAmount", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "token_id", + "type": "TokenIdentifier" + }, + { + "name": "max_amount", + "type": "BigUint" + } + ], + "outputs": [] + }, + { + "name": "getMaxBridgedAmount", + "mutability": "readonly", + "inputs": [ + { + "name": "token_id", + "type": "TokenIdentifier" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "pause", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "unpause", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "isPaused", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + } + ], + "promisesCallbackNames": [ + "transfer_callback" + ], + "events": [ + { + "identifier": "deposit", + "inputs": [ + { + "name": "dest_address", + "type": "Address", + "indexed": true + }, + { + "name": "tokens", + "type": "List", + "indexed": true + }, + { + "name": "event_data", + "type": "DepositEvent" + } + ] + }, + { + "identifier": "setStatusEvent", + "inputs": [ + { + "name": "batch_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_status", + "type": "TransactionStatus", + "indexed": true + } + ] + }, + { + "identifier": "addRefundTransactionEvent", + "inputs": [ + { + "name": "tx_id", + "type": "u64", + "indexed": true + }, + { + "name": "original_tx_id", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "transferPerformedEvent", + "inputs": [ + { + "name": "batch_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx", + "type": "Transaction" + } + ] + }, + { + "identifier": "transferFailedInvalidToken", + "inputs": [ + { + "name": "batch_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_id", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "transferFailedFrozenDestinationAccount", + "inputs": [ + { + "name": "batch_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_id", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "transferOverMaxAmount", + "inputs": [ + { + "name": "batch_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_id", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "transferFailedExecutionFailed", + "inputs": [ + { + "name": "batch_id", + "type": "u64", + "indexed": true + }, + { + "name": "tx_id", + "type": "u64", + "indexed": true + } + ] + } + ], + "hasCallback": true, + "types": { + "BatchStatus": { + "type": "enum", + "variants": [ + { + "name": "AlreadyProcessed", + "discriminant": 0 + }, + { + "name": "Empty", + "discriminant": 1 + }, + { + "name": "PartiallyFull", + "discriminant": 2, + "fields": [ + { + "name": "end_block_nonce", + "type": "u64" + }, + { + "name": "tx_ids", + "type": "List" + } + ] + }, + { + "name": "Full", + "discriminant": 3 + }, + { + "name": "WaitingForSignatures", + "discriminant": 4 + } + ] + }, + "DepositEvent": { + "type": "struct", + "fields": [ + { + "name": "tx_nonce", + "type": "u64" + }, + { + "name": "opt_function", + "type": "Option" + }, + { + "name": "opt_arguments", + "type": "Option>" + }, + { + "name": "opt_gas_limit", + "type": "Option" + } + ] + }, + "EsdtTokenPayment": { + "type": "struct", + "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "token_nonce", + "type": "u64" + }, + { + "name": "amount", + "type": "BigUint" + } + ] + }, + "EsdtTokenType": { + "type": "enum", + "variants": [ + { + "name": "Fungible", + "discriminant": 0 + }, + { + "name": "NonFungible", + "discriminant": 1 + }, + { + "name": "SemiFungible", + "discriminant": 2 + }, + { + "name": "Meta", + "discriminant": 3 + }, + { + "name": "Invalid", + "discriminant": 4 + } + ] + }, + "StolenFromFrameworkEsdtTokenData": { + "type": "struct", + "fields": [ + { + "name": "token_type", + "type": "EsdtTokenType" + }, + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "frozen", + "type": "bool" + }, + { + "name": "hash", + "type": "bytes" + }, + { + "name": "name", + "type": "bytes" + }, + { + "name": "attributes", + "type": "bytes" + }, + { + "name": "creator", + "type": "Address" + }, + { + "name": "royalties", + "type": "BigUint" + }, + { + "name": "uris", + "type": "List" + } + ] + }, + "Transaction": { + "type": "struct", + "fields": [ + { + "name": "block_nonce", + "type": "u64" + }, + { + "name": "nonce", + "type": "u64" + }, + { + "name": "from", + "type": "Address" + }, + { + "name": "to", + "type": "Address" + }, + { + "name": "tokens", + "type": "List" + }, + { + "name": "token_data", + "type": "List" + }, + { + "name": "opt_transfer_data", + "type": "Option" + }, + { + "name": "is_refund_tx", + "type": "bool" + } + ] + }, + "TransactionStatus": { + "type": "enum", + "variants": [ + { + "name": "None", + "discriminant": 0 + }, + { + "name": "Pending", + "discriminant": 1 + }, + { + "name": "InProgress", + "discriminant": 2 + }, + { + "name": "Executed", + "discriminant": 3 + }, + { + "name": "Rejected", + "discriminant": 4 + } + ] + }, + "TransferData": { + "type": "struct", + "fields": [ + { + "name": "gas_limit", + "type": "u64" + }, + { + "name": "function", + "type": "bytes" + }, + { + "name": "args", + "type": "List" + } + ] + } + } +} From 2c1d335bfa9627d45ea5e0754df1952d1baa9f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 26 Oct 2023 22:33:59 +0300 Subject: [PATCH 02/13] Sketch resultsParser.parseEvent(). --- src/interfaceOfNetwork.ts | 5 +++ src/smartcontracts/resultsParser.ts | 41 +++++++++++++++++--- src/smartcontracts/typesystem/abiRegistry.ts | 6 ++- src/smartcontracts/typesystem/event.ts | 19 ++++++--- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/interfaceOfNetwork.ts b/src/interfaceOfNetwork.ts index 2236f10f..09989e94 100644 --- a/src/interfaceOfNetwork.ts +++ b/src/interfaceOfNetwork.ts @@ -74,7 +74,12 @@ export interface ITransactionEvent { readonly address: IAddress; readonly identifier: string; readonly topics: ITransactionEventTopic[]; + + /** + * @deprecated Use dataPayload instead. + */ readonly data: string; + readonly dataPayload?: { valueOf(): Uint8Array }; findFirstOrNoneTopic(predicate: (topic: ITransactionEventTopic) => boolean): ITransactionEventTopic | undefined; getLastTopic(): ITransactionEventTopic; diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index b9122a38..e8e07b7d 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -2,12 +2,12 @@ import { TransactionDecoder, TransactionMetadata } from "@multiversx/sdk-transac import { Address } from "../address"; import { ErrCannotParseContractResults } from "../errors"; import { IAddress } from "../interface"; -import { IContractQueryResponse, IContractResults, ITransactionLogs, ITransactionOnNetwork } from "../interfaceOfNetwork"; +import { IContractQueryResponse, IContractResults, ITransactionEvent, ITransactionLogs, ITransactionOnNetwork } from "../interfaceOfNetwork"; import { Logger } from "../logger"; import { ArgSerializer } from "./argSerializer"; import { TypedOutcomeBundle, UntypedOutcomeBundle } from "./interface"; import { ReturnCode } from "./returnCode"; -import { EndpointDefinition, EndpointParameterDefinition, TypedValue } from "./typesystem"; +import { Type, TypedValue } from "./typesystem"; enum WellKnownEvents { OnTransactionCompleted = "completedTxEvent", @@ -23,9 +23,19 @@ interface IResultsParserOptions { argsSerializer: IArgsSerializer; } +interface IParameterDefinition { + type: Type; +} + +interface IEventInputDefinition { + type: Type; + indexed: boolean; +} + interface IArgsSerializer { - buffersToValues(buffers: Buffer[], parameters: EndpointParameterDefinition[]): TypedValue[]; + buffersToValues(buffers: Buffer[], parameters: IParameterDefinition[]): TypedValue[]; stringToBuffers(joinedString: string): Buffer[]; + stringToValues(joinedString: string, parameters: IParameterDefinition[]): TypedValue[]; } // TODO: perhaps move default construction options to a factory (ResultsParserFactory), instead of referencing them in the constructor @@ -46,7 +56,7 @@ export class ResultsParser { this.argsSerializer = options.argsSerializer; } - parseQueryResponse(queryResponse: IContractQueryResponse, endpoint: EndpointDefinition): TypedOutcomeBundle { + parseQueryResponse(queryResponse: IContractQueryResponse, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle { let parts = queryResponse.getReturnDataParts(); let values = this.argsSerializer.buffersToValues(parts, endpoint.output); let returnCode = new ReturnCode(queryResponse.returnCode.toString()); @@ -72,7 +82,7 @@ export class ResultsParser { }; } - parseOutcome(transaction: ITransactionOnNetwork, endpoint: EndpointDefinition): TypedOutcomeBundle { + parseOutcome(transaction: ITransactionOnNetwork, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle { let untypedBundle = this.parseUntypedOutcome(transaction); let values = this.argsSerializer.buffersToValues(untypedBundle.values, endpoint.output); @@ -315,4 +325,25 @@ export class ResultsParser { let returnCode = ReturnCode.fromBuffer(returnCodePart); return { returnCode, returnDataParts }; } + + parseEvent(transactionEvent: ITransactionEvent, eventDefinition: { inputs: IEventInputDefinition[] }): { + topics: TypedValue[], + dataParts: TypedValue[] + } { + const topics = transactionEvent.topics.map(topic => Buffer.from(topic.hex(), "hex")); + const data = transactionEvent.dataPayload?.valueOf() || Buffer.from([]); + const dataHex = data.toString("hex"); + + const indexedInputs = eventDefinition.inputs.filter(input => input.indexed); + const nonIndexedInputs = eventDefinition.inputs.filter(input => !input.indexed); + + // We skip the first topic, because it's the event identifier. + const decodedTopics = this.argsSerializer.buffersToValues(topics.slice(1), indexedInputs); + const decodedDataParts = this.argsSerializer.stringToValues(dataHex, nonIndexedInputs); + + return { + topics: decodedTopics, + dataParts: decodedDataParts + }; + } } diff --git a/src/smartcontracts/typesystem/abiRegistry.ts b/src/smartcontracts/typesystem/abiRegistry.ts index 3ae018c5..41976255 100644 --- a/src/smartcontracts/typesystem/abiRegistry.ts +++ b/src/smartcontracts/typesystem/abiRegistry.ts @@ -199,7 +199,11 @@ function mapEndpoint(endpoint: EndpointDefinition, mapper: TypeMapper): Endpoint function mapEvent(event: EventDefinition, mapper: TypeMapper): EventDefinition { const newInputs = event.inputs.map( - (e) => new EventTopicDefinition(e.name, mapper.mapType(e.type)) + (e) => new EventTopicDefinition({ + name: e.name, + type: mapper.mapType(e.type), + indexed: e.indexed + }) ); return new EventDefinition(event.identifier, newInputs); diff --git a/src/smartcontracts/typesystem/event.ts b/src/smartcontracts/typesystem/event.ts index b1356431..5b4dcac6 100644 --- a/src/smartcontracts/typesystem/event.ts +++ b/src/smartcontracts/typesystem/event.ts @@ -27,14 +27,21 @@ export class EventDefinition { export class EventTopicDefinition { readonly name: string; readonly type: Type; + readonly indexed: boolean; - constructor(name: string, type: Type) { - this.name = name; - this.type = type; + constructor(options: { name: string, type: Type, indexed: boolean }) { + this.name = options.name; + this.type = options.type; + this.indexed = options.indexed; } - static fromJSON(json: { name?: string, type: string }): EventTopicDefinition { - let parsedType = new TypeExpressionParser().parse(json.type); - return new EventTopicDefinition(json.name || NamePlaceholder, parsedType); + static fromJSON(json: { name?: string, type: string, indexed: boolean }): EventTopicDefinition { + const parsedType = new TypeExpressionParser().parse(json.type); + + return new EventTopicDefinition({ + name: json.name || NamePlaceholder, + type: parsedType, + indexed: json.indexed + }); } } From 8e66acc8ddf0a47fc17a64e11eb2fee4dcf8960e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 26 Oct 2023 22:48:34 +0300 Subject: [PATCH 03/13] Prettier result for parseEvent. Add unit test. --- src/smartcontracts/resultsParser.spec.ts | 33 ++++++++++++++++++++++++ src/smartcontracts/resultsParser.ts | 20 ++++++++------ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts index 25c0576b..4c42c778 100644 --- a/src/smartcontracts/resultsParser.spec.ts +++ b/src/smartcontracts/resultsParser.spec.ts @@ -1,10 +1,14 @@ import { ContractQueryResponse, ContractResultItem, ContractResults, TransactionEvent, TransactionEventTopic, TransactionLogs, TransactionOnNetwork } from "@multiversx/sdk-network-providers"; +import { TransactionEventData } from "@multiversx/sdk-network-providers/out/transactionEvents"; +import BigNumber from "bignumber.js"; import { assert } from "chai"; import * as fs from "fs"; import path from "path"; import { Address } from "../address"; +import { IAddress } from "../interface"; import { ITransactionOnNetwork } from "../interfaceOfNetwork"; import { LogLevel, Logger } from "../logger"; +import { loadAbiRegistry } from "../testutils"; import { ArgSerializer } from "./argSerializer"; import { ResultsParser } from "./resultsParser"; import { ReturnCode } from "./returnCode"; @@ -41,6 +45,9 @@ describe("test smart contract results parser", () => { }, stringToBuffers(_joinedString) { return [] + }, + stringToValues(_joinedString, _parameters) { + return [new U64Value(42)]; } } }); @@ -235,6 +242,32 @@ describe("test smart contract results parser", () => { assert.deepEqual(bundle.values, []); }); + it.only("should parse contract event", async () => { + const registry = await loadAbiRegistry("src/testdata/esdt-safe.abi.json"); + const depositEvent = registry.getEvent("deposit"); + + const event = new TransactionEvent({ + identifier: "deposit", + topics: [ + new TransactionEventTopic("ZGVwb3NpdA=="), + new TransactionEventTopic("cmzC1LRt1r10pMhNAnFb+FyudjGMq4G8CefCYdQUmmc="), + new TransactionEventTopic("AAAADFdFR0xELTAxZTQ5ZAAAAAAAAAAAAAAAAWQ="), + ], + dataPayload: new TransactionEventData(Buffer.from("AAAAAAAAA9sAAAA=", "base64")) + }); + + const bundle = parser.parseEvent(event, depositEvent); + + assert.equal((bundle.dest_address).bech32(), "erd1wfkv9495dhtt6a9yepxsyu2mlpw2ua333j4cr0qfulpxr4q5nfnshgyqun"); + assert.equal(bundle.tokens[0].token_identifier, "WEGLD-01e49d"); + assert.deepEqual(bundle.tokens[0].token_nonce, new BigNumber(0)); + assert.deepEqual(bundle.tokens[0].amount, new BigNumber(100)); + assert.deepEqual(bundle.event_data.tx_nonce, new BigNumber(987)); + assert.isNull(bundle.event_data.opt_function); + assert.isNull(bundle.event_data.opt_arguments); + assert.isNull(bundle.event_data.opt_gas_limit); + }); + // This test should be enabled manually and run against a set of sample transactions. // 2022-04-03: test ran against ~1800 transactions sampled from devnet. it.skip("should parse real-world contract outcomes", async () => { diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index e8e07b7d..e92ff244 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -28,6 +28,7 @@ interface IParameterDefinition { } interface IEventInputDefinition { + name: string; type: Type; indexed: boolean; } @@ -326,10 +327,8 @@ export class ResultsParser { return { returnCode, returnDataParts }; } - parseEvent(transactionEvent: ITransactionEvent, eventDefinition: { inputs: IEventInputDefinition[] }): { - topics: TypedValue[], - dataParts: TypedValue[] - } { + parseEvent(transactionEvent: ITransactionEvent, eventDefinition: { inputs: IEventInputDefinition[] }): any { + const result: any = {}; const topics = transactionEvent.topics.map(topic => Buffer.from(topic.hex(), "hex")); const data = transactionEvent.dataPayload?.valueOf() || Buffer.from([]); const dataHex = data.toString("hex"); @@ -341,9 +340,14 @@ export class ResultsParser { const decodedTopics = this.argsSerializer.buffersToValues(topics.slice(1), indexedInputs); const decodedDataParts = this.argsSerializer.stringToValues(dataHex, nonIndexedInputs); - return { - topics: decodedTopics, - dataParts: decodedDataParts - }; + for (let i = 0; i < indexedInputs.length; i++) { + result[indexedInputs[i].name] = decodedTopics[i].valueOf(); + } + + for (let i = 0; i < nonIndexedInputs.length; i++) { + result[nonIndexedInputs[i].name] = decodedDataParts[i].valueOf(); + } + + return result; } } From d347ef10f68ac1100dd6c4041e12b1ca5eb956d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 26 Oct 2023 22:55:52 +0300 Subject: [PATCH 04/13] Fix typo (naming). --- src/smartcontracts/argSerializer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smartcontracts/argSerializer.ts b/src/smartcontracts/argSerializer.ts index e9e8f214..d4dbeafb 100644 --- a/src/smartcontracts/argSerializer.ts +++ b/src/smartcontracts/argSerializer.ts @@ -21,7 +21,7 @@ interface IParameterDefinition { // TODO: perhaps move default construction options to a factory (ArgSerializerFactory), instead of referencing them in the constructor // (postpone as much as possible, breaking change) -const defaultArgSerializerrOptions: IArgSerializerOptions = { +const defaultArgSerializerOptions: IArgSerializerOptions = { codec: new BinaryCodec() }; @@ -29,7 +29,7 @@ export class ArgSerializer { codec: ICodec; constructor(options?: IArgSerializerOptions) { - options = { ...defaultArgSerializerrOptions, ...options }; + options = { ...defaultArgSerializerOptions, ...options }; this.codec = options.codec; } From bf49ac2ba26119baab895e5f2736c5e5457eaa26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 27 Oct 2023 22:51:20 +0300 Subject: [PATCH 05/13] Adjust parseEvent, add extra unit tests. --- src/smartcontracts/resultsParser.spec.ts | 58 +++++++++++++++++++++--- src/smartcontracts/resultsParser.ts | 15 +++--- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts index 4c42c778..34a3589b 100644 --- a/src/smartcontracts/resultsParser.spec.ts +++ b/src/smartcontracts/resultsParser.spec.ts @@ -12,7 +12,7 @@ import { loadAbiRegistry } from "../testutils"; import { ArgSerializer } from "./argSerializer"; import { ResultsParser } from "./resultsParser"; import { ReturnCode } from "./returnCode"; -import { BigUIntType, BigUIntValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, StringType, StringValue, TypedValue, U32Type, U32Value, U64Type, U64Value, VariadicType, VariadicValue } from "./typesystem"; +import { AbiRegistry, BigUIntType, BigUIntValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, StringType, StringValue, TypedValue, U32Type, U32Value, U64Type, U64Value, VariadicType, VariadicValue } from "./typesystem"; import { BytesType, BytesValue } from "./typesystem/bytes"; const KnownReturnCodes: string[] = [ @@ -242,12 +242,11 @@ describe("test smart contract results parser", () => { assert.deepEqual(bundle.values, []); }); - it.only("should parse contract event", async () => { - const registry = await loadAbiRegistry("src/testdata/esdt-safe.abi.json"); - const depositEvent = registry.getEvent("deposit"); + it("should parse contract event", async () => { + const abiRegistry = await loadAbiRegistry("src/testdata/esdt-safe.abi.json"); + const eventDefinition = abiRegistry.getEvent("deposit"); const event = new TransactionEvent({ - identifier: "deposit", topics: [ new TransactionEventTopic("ZGVwb3NpdA=="), new TransactionEventTopic("cmzC1LRt1r10pMhNAnFb+FyudjGMq4G8CefCYdQUmmc="), @@ -256,7 +255,7 @@ describe("test smart contract results parser", () => { dataPayload: new TransactionEventData(Buffer.from("AAAAAAAAA9sAAAA=", "base64")) }); - const bundle = parser.parseEvent(event, depositEvent); + const bundle = parser.parseEvent(event, eventDefinition); assert.equal((bundle.dest_address).bech32(), "erd1wfkv9495dhtt6a9yepxsyu2mlpw2ua333j4cr0qfulpxr4q5nfnshgyqun"); assert.equal(bundle.tokens[0].token_identifier, "WEGLD-01e49d"); @@ -268,6 +267,53 @@ describe("test smart contract results parser", () => { assert.isNull(bundle.event_data.opt_gas_limit); }); + it("should parse contract event (with multi-values)", async () => { + const abiRegistry = AbiRegistry.create({ + "events": [ + { + "identifier": "foobar", + "inputs": [ + { + "name": "a", + "type": "multi", + "indexed": true + }, + { + "name": "b", + "type": "multi", + "indexed": true + }, + { + "name": "c", + "type": "variadic", + "indexed": false + } + ] + } + ] + }); + + const eventDefinition = abiRegistry.getEvent("foobar"); + + const event = new TransactionEvent({ + topics: [ + new TransactionEventTopic(Buffer.from("not used").toString("base64")), + new TransactionEventTopic(Buffer.from([42]).toString("base64")), + new TransactionEventTopic(Buffer.from("test").toString("base64")), + new TransactionEventTopic(Buffer.from([43]).toString("base64")), + new TransactionEventTopic(Buffer.from("test").toString("base64")), + new TransactionEventTopic(Buffer.from("test").toString("base64")), + new TransactionEventTopic(Buffer.from([44]).toString("base64")), + ], + dataPayload: new TransactionEventData(Buffer.from("01@02@03@04", "hex")) + }); + + const bundle = parser.parseEvent(event, eventDefinition); + assert.deepEqual(bundle.a, [new BigNumber(42), "test", new BigNumber(43), "test"]); + assert.deepEqual(bundle.b, ["test", new BigNumber(44)]); + assert.deepEqual(bundle.c, [new BigNumber(1), new BigNumber(2), new BigNumber(3), new BigNumber(4)]); + }); + // This test should be enabled manually and run against a set of sample transactions. // 2022-04-03: test ran against ~1800 transactions sampled from devnet. it.skip("should parse real-world contract outcomes", async () => { diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index e92ff244..c90c7092 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -329,21 +329,24 @@ export class ResultsParser { parseEvent(transactionEvent: ITransactionEvent, eventDefinition: { inputs: IEventInputDefinition[] }): any { const result: any = {}; - const topics = transactionEvent.topics.map(topic => Buffer.from(topic.hex(), "hex")); + + // We skip the first topic, because that's the event identifier. + const topics = transactionEvent.topics.map(topic => Buffer.from(topic.hex(), "hex")).slice(1); const data = transactionEvent.dataPayload?.valueOf() || Buffer.from([]); const dataHex = data.toString("hex"); + // "Indexed" ABI "event.inputs" correspond to "event.topics[1:]": const indexedInputs = eventDefinition.inputs.filter(input => input.indexed); - const nonIndexedInputs = eventDefinition.inputs.filter(input => !input.indexed); - - // We skip the first topic, because it's the event identifier. - const decodedTopics = this.argsSerializer.buffersToValues(topics.slice(1), indexedInputs); - const decodedDataParts = this.argsSerializer.stringToValues(dataHex, nonIndexedInputs); + const decodedTopics = this.argsSerializer.buffersToValues(topics, indexedInputs); for (let i = 0; i < indexedInputs.length; i++) { result[indexedInputs[i].name] = decodedTopics[i].valueOf(); } + // "Non-indexed" ABI "event.inputs" correspond to "event.data": + const nonIndexedInputs = eventDefinition.inputs.filter(input => !input.indexed); + const decodedDataParts = this.argsSerializer.stringToValues(dataHex, nonIndexedInputs); + for (let i = 0; i < nonIndexedInputs.length; i++) { result[nonIndexedInputs[i].name] = decodedDataParts[i].valueOf(); } From 44fc394e26b20e2af3d8f8cfec06e0b1cf0882c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 27 Oct 2023 23:38:10 +0300 Subject: [PATCH 06/13] Upwards compatibility etc. --- package-lock.json | 4 +- package.json | 2 +- src/interfaceOfNetwork.ts | 5 -- src/smartcontracts/resultsParser.spec.ts | 60 ++++++++++++++++++++---- src/smartcontracts/resultsParser.ts | 26 +++++++--- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c585c0d..b01d3d01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "12.12.0", + "version": "12.13.0-beta.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "12.12.0", + "version": "12.13.0-beta.1", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 74ae6b1a..61ced3ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "12.12.0", + "version": "12.13.0-beta.1", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/interfaceOfNetwork.ts b/src/interfaceOfNetwork.ts index 09989e94..2236f10f 100644 --- a/src/interfaceOfNetwork.ts +++ b/src/interfaceOfNetwork.ts @@ -74,12 +74,7 @@ export interface ITransactionEvent { readonly address: IAddress; readonly identifier: string; readonly topics: ITransactionEventTopic[]; - - /** - * @deprecated Use dataPayload instead. - */ readonly data: string; - readonly dataPayload?: { valueOf(): Uint8Array }; findFirstOrNoneTopic(predicate: (topic: ITransactionEventTopic) => boolean): ITransactionEventTopic | undefined; getLastTopic(): ITransactionEventTopic; diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts index 34a3589b..e257cc07 100644 --- a/src/smartcontracts/resultsParser.spec.ts +++ b/src/smartcontracts/resultsParser.spec.ts @@ -45,9 +45,6 @@ describe("test smart contract results parser", () => { }, stringToBuffers(_joinedString) { return [] - }, - stringToValues(_joinedString, _parameters) { - return [new U64Value(42)]; } } }); @@ -285,7 +282,7 @@ describe("test smart contract results parser", () => { }, { "name": "c", - "type": "variadic", + "type": "u8", "indexed": false } ] @@ -295,7 +292,7 @@ describe("test smart contract results parser", () => { const eventDefinition = abiRegistry.getEvent("foobar"); - const event = new TransactionEvent({ + const event = { topics: [ new TransactionEventTopic(Buffer.from("not used").toString("base64")), new TransactionEventTopic(Buffer.from([42]).toString("base64")), @@ -305,13 +302,60 @@ describe("test smart contract results parser", () => { new TransactionEventTopic(Buffer.from("test").toString("base64")), new TransactionEventTopic(Buffer.from([44]).toString("base64")), ], - dataPayload: new TransactionEventData(Buffer.from("01@02@03@04", "hex")) - }); + dataPayload: new TransactionEventData(Buffer.from([42])) + }; const bundle = parser.parseEvent(event, eventDefinition); assert.deepEqual(bundle.a, [new BigNumber(42), "test", new BigNumber(43), "test"]); assert.deepEqual(bundle.b, ["test", new BigNumber(44)]); - assert.deepEqual(bundle.c, [new BigNumber(1), new BigNumber(2), new BigNumber(3), new BigNumber(4)]); + assert.deepEqual(bundle.c, new BigNumber(42)); + }); + + it("should parse contract event (Sirius)", async () => { + const abiRegistry = AbiRegistry.create({ + "events": [ + { + "identifier": "foobar", + "inputs": [ + { + "name": "a", + "type": "u8", + "indexed": true + }, + { + "name": "b", + "type": "u8", + "indexed": false + }, + { + "name": "c", + "type": "u8", + "indexed": false + } + ] + } + ] + }); + + const eventDefinition = abiRegistry.getEvent("foobar"); + + const event = { + topics: [ + new TransactionEventTopic(Buffer.from("not used").toString("base64")), + new TransactionEventTopic(Buffer.from([42]).toString("base64")), + ], + additionalData: [ + new TransactionEventData(Buffer.from([43])), + new TransactionEventData(Buffer.from([44])) + ], + // Will be ignored. + dataPayload: new TransactionEventData(Buffer.from([43])), + }; + + const bundle = parser.parseEvent(event, eventDefinition); + assert.deepEqual(bundle.a, new BigNumber(42)); + assert.deepEqual(bundle.b, new BigNumber(43)); + assert.deepEqual(bundle.c, new BigNumber(44)); }); // This test should be enabled manually and run against a set of sample transactions. diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index c90c7092..6186e8e6 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -2,7 +2,7 @@ import { TransactionDecoder, TransactionMetadata } from "@multiversx/sdk-transac import { Address } from "../address"; import { ErrCannotParseContractResults } from "../errors"; import { IAddress } from "../interface"; -import { IContractQueryResponse, IContractResults, ITransactionEvent, ITransactionLogs, ITransactionOnNetwork } from "../interfaceOfNetwork"; +import { IContractQueryResponse, IContractResults, ITransactionLogs, ITransactionOnNetwork } from "../interfaceOfNetwork"; import { Logger } from "../logger"; import { ArgSerializer } from "./argSerializer"; import { TypedOutcomeBundle, UntypedOutcomeBundle } from "./interface"; @@ -33,10 +33,15 @@ interface IEventInputDefinition { indexed: boolean; } +interface ITransactionEvent { + readonly topics: { valueOf(): Uint8Array }[]; + readonly dataPayload?: { valueOf(): Uint8Array }; + readonly additionalData?: { valueOf(): Uint8Array }[]; +} + interface IArgsSerializer { buffersToValues(buffers: Buffer[], parameters: IParameterDefinition[]): TypedValue[]; stringToBuffers(joinedString: string): Buffer[]; - stringToValues(joinedString: string, parameters: IParameterDefinition[]): TypedValue[]; } // TODO: perhaps move default construction options to a factory (ResultsParserFactory), instead of referencing them in the constructor @@ -331,9 +336,18 @@ export class ResultsParser { const result: any = {}; // We skip the first topic, because that's the event identifier. - const topics = transactionEvent.topics.map(topic => Buffer.from(topic.hex(), "hex")).slice(1); - const data = transactionEvent.dataPayload?.valueOf() || Buffer.from([]); - const dataHex = data.toString("hex"); + const topics = transactionEvent.topics.map(topic => Buffer.from(topic.valueOf())).slice(1); + // < Sirius. + const legacyData = transactionEvent.dataPayload?.valueOf() || Buffer.from([]); + // >= Sirius. + const additionalData = transactionEvent.additionalData?.map(data => Buffer.from(data.valueOf())) || []; + + // < Sirius. + if (additionalData.length == 0) { + if (legacyData.length > 0) { + additionalData.push(Buffer.from(legacyData)); + } + } // "Indexed" ABI "event.inputs" correspond to "event.topics[1:]": const indexedInputs = eventDefinition.inputs.filter(input => input.indexed); @@ -345,7 +359,7 @@ export class ResultsParser { // "Non-indexed" ABI "event.inputs" correspond to "event.data": const nonIndexedInputs = eventDefinition.inputs.filter(input => !input.indexed); - const decodedDataParts = this.argsSerializer.stringToValues(dataHex, nonIndexedInputs); + const decodedDataParts = this.argsSerializer.buffersToValues(additionalData, nonIndexedInputs); for (let i = 0; i < nonIndexedInputs.length; i++) { result[nonIndexedInputs[i].name] = decodedDataParts[i].valueOf(); From 9d0bb7323e50cb5df2ef8ba82940cc1a4ca02296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 27 Oct 2023 23:43:52 +0300 Subject: [PATCH 07/13] Some deprecations. --- src/interfaceOfNetwork.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/interfaceOfNetwork.ts b/src/interfaceOfNetwork.ts index 2236f10f..07edeb9e 100644 --- a/src/interfaceOfNetwork.ts +++ b/src/interfaceOfNetwork.ts @@ -66,7 +66,15 @@ export interface ITransactionLogs { events: ITransactionEvent[]; findSingleOrNoneEvent(identifier: string, predicate?: (event: ITransactionEvent) => boolean): ITransactionEvent | undefined; + + /** + * @deprecated Will be removed from the interface (with no replacement). Not used in "sdk-core". + */ findFirstOrNoneEvent(identifier: string, predicate?: (event: ITransactionEvent) => boolean): ITransactionEvent | undefined; + + /** + * @deprecated Will be removed from the interface (with no replacement). Not used in "sdk-core". + */ findEvents(identifier: string, predicate?: (event: ITransactionEvent) => boolean): ITransactionEvent[]; } From 2f1bb27ba20c14911a05faf5210af6a2c300e484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 27 Oct 2023 23:46:57 +0300 Subject: [PATCH 08/13] Fix link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee955dab..83d3a5a4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ MultiversX SDK for JavaScript and TypeScript (written in TypeScript). ## Documentation - - [Cookbook](https://docs.multiversx.com/sdk-and-tools/erdjs/erdjs-cookbook/) + - [Cookbook](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook/) ## Distribution From ae211c88d9235dd9c6ed2abc218cfd4cb3c9d31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 8 Nov 2023 13:14:24 +0200 Subject: [PATCH 09/13] Fix applySignature(). Handle buffers in a better way. --- src/signableMessage.ts | 7 ++----- src/signature.ts | 12 +++++++++++- src/transaction.ts | 16 +++------------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/signableMessage.ts b/src/signableMessage.ts index fbdb8f67..4aee73fa 100644 --- a/src/signableMessage.ts +++ b/src/signableMessage.ts @@ -1,5 +1,6 @@ import { Address } from "./address"; import { ISignature } from "./interface"; +import { interpretSignatureAsBuffer } from "./signature"; const createKeccakHash = require("keccak"); export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; @@ -57,11 +58,7 @@ export class SignableMessage { } applySignature(signature: ISignature | Buffer) { - if (signature instanceof Buffer) { - this.signature = signature; - } else { - this.signature = Buffer.from(signature.hex(), "hex"); - } + this.signature = interpretSignatureAsBuffer(signature); } getMessageSize(): Buffer { diff --git a/src/signature.ts b/src/signature.ts index 9ff9e9a3..e307a225 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,4 +1,4 @@ -import * as errors from "./errors"; +import * as errors from "./errors"; const SIGNATURE_LENGTH = 64; @@ -58,3 +58,13 @@ export class Signature { return this.valueHex; } } + +export function interpretSignatureAsBuffer(signature: { hex(): string; } | Uint8Array): Buffer { + if (ArrayBuffer.isView(signature)) { + return Buffer.from(signature); + } else if ((signature).hex != null) { + return Buffer.from(signature.hex(), "hex"); + } + + throw new Error(`Object cannot be interpreted as a signature: ${signature}`); +} diff --git a/src/transaction.ts b/src/transaction.ts index 092aae7d..e8926c44 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -8,7 +8,7 @@ import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, IPlainTransactionObje import { INetworkConfig } from "./interfaceOfNetwork"; import { TransactionOptions, TransactionVersion } from "./networkParams"; import { ProtoSerializer } from "./proto"; -import { Signature } from "./signature"; +import { Signature, interpretSignatureAsBuffer } from "./signature"; import { TransactionPayload } from "./transactionPayload"; import { guardNotEmpty } from "./utils"; @@ -372,27 +372,17 @@ export class Transaction { * @param signature The signature, as computed by a signer. */ applySignature(signature: ISignature | Uint8Array) { - this.signature = this.interpretSignatureAsBuffer(signature); + this.signature = interpretSignatureAsBuffer(signature); this.hash = TransactionHash.compute(this); } - private interpretSignatureAsBuffer(signature: ISignature | Uint8Array): Buffer { - if (ArrayBuffer.isView(signature)) { - return Buffer.from(signature); - } else if ((signature).hex != null) { - return Buffer.from(signature.hex(), "hex"); - } - - throw new Error(`Object cannot be interpreted as a signature: ${signature}`); - } - /** * Applies the guardian signature on the transaction. * * @param guardianSignature The signature, as computed by a signer. */ applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { - this.guardianSignature = this.interpretSignatureAsBuffer(guardianSignature); + this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); this.hash = TransactionHash.compute(this); } From e034fe24d570fb8013ed788eac6c59d800a775d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 8 Nov 2023 13:16:38 +0200 Subject: [PATCH 10/13] Bump version. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff363cb1..f25dd1bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "12.12.0", + "version": "12.13.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "12.12.0", + "version": "12.13.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 74ae6b1a..29314cf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "12.12.0", + "version": "12.13.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", From 0aa4869fbaa4c77c541d83bfd60d86677c71dad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 8 Nov 2023 14:06:46 +0200 Subject: [PATCH 11/13] Adjust interface. --- src/signableMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signableMessage.ts b/src/signableMessage.ts index 4aee73fa..4fe82b7e 100644 --- a/src/signableMessage.ts +++ b/src/signableMessage.ts @@ -57,7 +57,7 @@ export class SignableMessage { return this.signature; } - applySignature(signature: ISignature | Buffer) { + applySignature(signature: ISignature | Uint8Array) { this.signature = interpretSignatureAsBuffer(signature); } From aa46b04e8b5361e31fd6020f68badecf430a104b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 9 Nov 2023 12:15:17 +0200 Subject: [PATCH 12/13] Bump version. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f25dd1bf..266b424f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "12.13.0", + "version": "12.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "12.13.0", + "version": "12.14.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 29314cf9..03a9351f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "12.13.0", + "version": "12.14.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", From 90b8c948b5bbba99d5b7c97b05a14f8b43ba4b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 15 Nov 2023 17:27:04 +0200 Subject: [PATCH 13/13] Bump version. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 266b424f..eb20d865 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "12.14.0", + "version": "13.0.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "12.14.0", + "version": "13.0.0-alpha.1", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 03a9351f..64d6b594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "12.14.0", + "version": "13.0.0-alpha.1", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js",