From 2917009404dd7d50f0e2ec569b28577f07caa57a Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 15 Feb 2024 11:54:01 +0200 Subject: [PATCH 1/4] add token management transactions parser --- src/errors.ts | 275 ++++----- .../tokenOperationsOutcomeParser.ts | 22 +- src/transactionsOutcomeParsers/resources.ts | 55 ++ ...anagementTransactionsOutcomeParser.spec.ts | 560 ++++++++++++++++++ ...okenManagementTransactionsOutcomeParser.ts | 361 +++++++++++ 5 files changed, 1131 insertions(+), 142 deletions(-) create mode 100644 src/transactionsOutcomeParsers/resources.ts create mode 100644 src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts create mode 100644 src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts diff --git a/src/errors.ts b/src/errors.ts index 1f23fd1c..30310edc 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -2,362 +2,373 @@ * The base class for exceptions (errors). */ export class Err extends Error { - inner: Error | undefined = undefined; + inner: Error | undefined = undefined; - public constructor(message: string, inner?: Error) { - super(message); - this.inner = inner; - } + public constructor(message: string, inner?: Error) { + super(message); + this.inner = inner; + } - /** - * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). - */ - summary(): any[] { - let result = []; + /** + * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). + */ + summary(): any[] { + let result = []; - result.push({ name: this.name, message: this.message }); + result.push({ name: this.name, message: this.message }); - let inner: any = this.inner; - while (inner) { - result.push({ name: inner.name, message: inner.message }); - inner = inner.inner; - } + let inner: any = this.inner; + while (inner) { + result.push({ name: inner.name, message: inner.message }); + inner = inner.inner; + } - return result; - } + return result; + } } /** * Signals invalid arguments for a function, for an operation. */ export class ErrInvalidArgument extends Err { - public constructor(message: string, inner?: Error) { - super(`Invalid argument: ${message}`, inner); - } + public constructor(message: string, inner?: Error) { + super(`Invalid argument: ${message}`, inner); + } } /** * Signals an unsupported operation. */ export class ErrUnsupportedOperation extends Err { - public constructor(operation: string, reason: string = "not specified") { - super(`Operation "${operation}" not supported. Reason: ${reason}`); - } + public constructor(operation: string, reason: string = "not specified") { + super(`Operation "${operation}" not supported. Reason: ${reason}`); + } } /** * Signals the provisioning of objects of unexpected (bad) types. */ export class ErrBadType extends Err { - public constructor(name: string, type: any, value?: any) { - super(`Bad type of "${name}": ${value}. Expected type: ${type}`); - } + public constructor(name: string, type: any, value?: any) { + super(`Bad type of "${name}": ${value}. Expected type: ${type}`); + } } /** * Signals that an invariant failed. */ export class ErrInvariantFailed extends Err { - public constructor(message: string) { - super(`Invariant failed: [${message}]`); - } + public constructor(message: string) { + super(`Invariant failed: [${message}]`); + } } /** * Signals an unexpected condition. */ export class ErrUnexpectedCondition extends Err { - public constructor(message: string) { - super(`Unexpected condition: [${message}]`); - } + public constructor(message: string) { + super(`Unexpected condition: [${message}]`); + } } /** * Signals issues with {@link Address} instantiation. */ export class ErrAddressCannotCreate extends Err { - public constructor(input: any, inner?: Error) { - let message = `Cannot create address from: ${input}`; - super(message, inner); - } + public constructor(input: any, inner?: Error) { + let message = `Cannot create address from: ${input}`; + super(message, inner); + } } /** * Signals issues with the HRP of an {@link Address}. */ export class ErrAddressBadHrp extends Err { - public constructor(expected: string, got: string) { - super(`Wrong address HRP. Expected: ${expected}, got ${got}`); - } + public constructor(expected: string, got: string) { + super(`Wrong address HRP. Expected: ${expected}, got ${got}`); + } } /** * Signals the presence of an empty / invalid address. */ export class ErrAddressEmpty extends Err { - public constructor() { - super(`Address is empty`); - } + public constructor() { + super(`Address is empty`); + } } /** * Signals an invalid value for {@link GasLimit} objects. */ export class ErrNotEnoughGas extends Err { - public constructor(value: number) { - super(`Not enough gas provided: ${value}`); - } + public constructor(value: number) { + super(`Not enough gas provided: ${value}`); + } } /** * Signals an invalid value for {@link Nonce} objects. */ export class ErrNonceInvalid extends Err { - public constructor(value: number) { - super(`Invalid nonce: ${value}`); - } + public constructor(value: number) { + super(`Invalid nonce: ${value}`); + } } /** * Signals an invalid value for {@link TransactionVersion} objects. */ export class ErrTransactionVersionInvalid extends Err { - public constructor(value: number) { - super(`Invalid transaction version: ${value}`); - } + public constructor(value: number) { + super(`Invalid transaction version: ${value}`); + } } /** * Signals an invalid value for {@link TransactionOptions} objects. */ export class ErrTransactionOptionsInvalid extends Err { - public constructor(value: number) { - super(`Invalid transaction options: ${value}`); - } + public constructor(value: number) { + super(`Invalid transaction options: ${value}`); + } } /** * Signals an error related to signing a message (a transaction). */ export class ErrSignatureCannotCreate extends Err { - public constructor(input: any, inner?: Error) { - let message = `Cannot create signature from: ${input}`; - super(message, inner); - } + public constructor(input: any, inner?: Error) { + let message = `Cannot create signature from: ${input}`; + super(message, inner); + } } /** * Signals an invalid value for the name of a {@link ContractFunction}. */ export class ErrInvalidFunctionName extends Err { - public constructor() { - super(`Invalid function name`); - } + public constructor() { + super(`Invalid function name`); + } } /** * Signals a failed operation, since the Timer is already running. */ export class ErrAsyncTimerAlreadyRunning extends Err { - public constructor() { - super("Async timer already running"); - } + public constructor() { + super("Async timer already running"); + } } /** * Signals a failed operation, since the Timer has been aborted. */ export class ErrAsyncTimerAborted extends Err { - public constructor() { - super("Async timer aborted"); - } + public constructor() { + super("Async timer aborted"); + } } /** * Signals a timout for a {@link TransactionWatcher}. */ export class ErrTransactionWatcherTimeout extends Err { - public constructor() { - super(`TransactionWatcher has timed out`); - } + public constructor() { + super(`TransactionWatcher has timed out`); + } } /** * Signals an issue related to waiting for a specific transaction status. */ export class ErrExpectedTransactionStatusNotReached extends Err { - public constructor() { - super(`Expected transaction status not reached`); - } + public constructor() { + super(`Expected transaction status not reached`); + } } /** * Signals an issue related to waiting for specific transaction events. */ export class ErrExpectedTransactionEventsNotFound extends Err { - public constructor() { - super(`Expected transaction events not found`); - } + public constructor() { + super(`Expected transaction events not found`); + } } /** * Signals a generic error in the context of Smart Contracts. */ export class ErrContract extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } export class ErrContractHasNoAddress extends ErrContract { - public constructor() { - super(` + public constructor() { + super(` The smart contract has no address set. Make sure you provide the address in the constructor, or call setAddress() appropriately. If you need to recompute the address of the contract, make use of SmartContract.computeAddress() (static method). `); - } + } } /** * Signals an error thrown by the mock-like test objects. */ export class ErrMock extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic type error. */ export class ErrTypingSystem extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a missing field on a struct. */ export class ErrMissingFieldOnStruct extends Err { - public constructor(fieldName: string, structName: string) { - super(`field ${fieldName} does not exist on struct ${structName}`); - } + public constructor(fieldName: string, structName: string) { + super(`field ${fieldName} does not exist on struct ${structName}`); + } } /** * Signals a missing field on an enum. */ export class ErrMissingFieldOnEnum extends Err { - public constructor(fieldName: string, enumName: string) { - super(`field ${fieldName} does not exist on enum ${enumName}`); - } + public constructor(fieldName: string, enumName: string) { + super(`field ${fieldName} does not exist on enum ${enumName}`); + } } /** * Signals an error when parsing the contract results. */ export class ErrCannotParseContractResults extends Err { - public constructor(details: string) { - super(`cannot parse contract results: ${details}`); - } + public constructor(details: string) { + super(`cannot parse contract results: ${details}`); + } } /** * Signals an error when parsing the outcome of a transaction (results and logs). */ export class ErrCannotParseTransactionOutcome extends Err { - public constructor(transactionHash: string, message: string) { - super(`cannot parse outcome of transaction ${transactionHash}: ${message}`); - } + public constructor(transactionHash: string, message: string) { + super(`cannot parse outcome of transaction ${transactionHash}: ${message}`); + } } /** * Signals a generic codec (encode / decode) error. */ export class ErrCodec extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic contract interaction error. */ export class ErrContractInteraction extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals that a method is not yet implemented */ export class ErrNotImplemented extends Err { - public constructor() { - super("Method not yet implemented"); - } + public constructor() { + super("Method not yet implemented"); + } } /** * Signals invalid arguments when using the relayed v1 builder */ export class ErrInvalidRelayedV1BuilderArguments extends Err { - public constructor() { - super("invalid arguments for relayed v1 builder"); - } + public constructor() { + super("invalid arguments for relayed v1 builder"); + } } /** * Signals invalid arguments when using the relayed v2 builder */ export class ErrInvalidRelayedV2BuilderArguments extends Err { - public constructor() { - super("invalid arguments for relayed v2 builder"); - } + public constructor() { + super("invalid arguments for relayed v2 builder"); + } } /** * Signals that Gas Limit isn't 0 for an inner tx when using relayed v2 builder */ export class ErrGasLimitShouldBe0ForInnerTransaction extends Err { - public constructor() { - super("gas limit must be 0 for the inner transaction for relayed v2"); - } + public constructor() { + super("gas limit must be 0 for the inner transaction for relayed v2"); + } } /** * Signals that the `isCompleted` property is missing on the transaction obect and is needed for the Transaction Watcher */ export class ErrIsCompletedFieldIsMissingOnTransaction extends Err { - public constructor() { - super("The transaction watcher requires the `isCompleted` property to be defined on the transaction object. Perhaps you've used the sdk-network-provider's `ProxyNetworkProvider.getTransaction()` and in that case you should also pass `withProcessStatus=true`.") - } + public constructor() { + super( + "The transaction watcher requires the `isCompleted` property to be defined on the transaction object. Perhaps you've used the sdk-network-provider's `ProxyNetworkProvider.getTransaction()` and in that case you should also pass `withProcessStatus=true`.", + ); + } } /** * Signals that the provided token identifier is not valid */ export class ErrInvalidTokenIdentifier extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic bad usage error */ export class ErrBadUsage extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals an invalid inner transaction for relayed transactions */ -export class ErrInvalidInnerTransaction extends Err{ - public constructor(message: string){ - super(message); - } +export class ErrInvalidInnerTransaction extends Err { + public constructor(message: string) { + super(message); + } +} + +/** + * Signals an error when parsing the logs of a transaction. + */ +export class ErrParseTransactionOutcome extends Err { + public constructor(message: string) { + super(message); + } } diff --git a/src/tokenOperations/tokenOperationsOutcomeParser.ts b/src/tokenOperations/tokenOperationsOutcomeParser.ts index 260ffb3d..8fc489f3 100644 --- a/src/tokenOperations/tokenOperationsOutcomeParser.ts +++ b/src/tokenOperations/tokenOperationsOutcomeParser.ts @@ -3,7 +3,6 @@ import { ErrCannotParseTransactionOutcome } from "../errors"; import { IAddress } from "../interface"; import { bufferToBigInt } from "./codec"; - interface ITransactionOnNetwork { hash: string; contractResults: IContractResults; @@ -42,8 +41,7 @@ export interface IRegisterAndSetAllRolesOutcome { roles: string[]; } -export interface IToggleBurnRoleGloballyOutcome { -} +export interface IToggleBurnRoleGloballyOutcome {} export interface ISetSpecialRoleOutcome { userAddress: string; @@ -71,8 +69,7 @@ export interface IBurnOutcome { burntSupply: string; } -export interface IPausingOutcome { -} +export interface IPausingOutcome {} export interface IFreezingOutcome { userAddress: string; @@ -106,6 +103,9 @@ export interface IBurnQuantityOutcome { burntQuantity: string; } +/** + * @deprecated Use {@link TokenManagementTransactionsOutcomeParser} + */ export class TokenOperationsOutcomeParser { parseIssueFungible(transaction: ITransactionOnNetwork): IESDTIssueOutcome { this.ensureNoError(transaction); @@ -146,7 +146,7 @@ export class TokenOperationsOutcomeParser { const tokenIdentifier = this.extractTokenIdentifier(eventRegister); const eventSetRole = this.findSingleEventByIdentifier(transaction, "ESDTSetRole"); - const roles = eventSetRole.topics.slice(3).map(topic => topic.valueOf().toString()); + const roles = eventSetRole.topics.slice(3).map((topic) => topic.valueOf().toString()); return { tokenIdentifier, roles }; } @@ -167,7 +167,7 @@ export class TokenOperationsOutcomeParser { const event = this.findSingleEventByIdentifier(transaction, "ESDTSetRole"); const userAddress = event.address.toString(); const tokenIdentifier = this.extractTokenIdentifier(event); - const roles = event.topics.slice(3).map(topic => topic.valueOf().toString()); + const roles = event.topics.slice(3).map((topic) => topic.valueOf().toString()); return { userAddress, tokenIdentifier, roles }; } @@ -284,13 +284,16 @@ export class TokenOperationsOutcomeParser { const data = Buffer.from(event.data.substring(1), "hex").toString(); const message = event.topics[1]?.valueOf().toString(); - throw new ErrCannotParseTransactionOutcome(transaction.hash, `encountered signalError: ${message} (${data})`); + throw new ErrCannotParseTransactionOutcome( + transaction.hash, + `encountered signalError: ${message} (${data})`, + ); } } } private findSingleEventByIdentifier(transaction: ITransactionOnNetwork, identifier: string): ITransactionEvent { - const events = this.gatherAllEvents(transaction).filter(event => event.identifier == identifier); + const events = this.gatherAllEvents(transaction).filter((event) => event.identifier == identifier); if (events.length == 0) { throw new ErrCannotParseTransactionOutcome(transaction.hash, `cannot find event of type ${identifier}`); @@ -330,4 +333,3 @@ export class TokenOperationsOutcomeParser { return Address.fromBuffer(event.topics[3]?.valueOf()).toString(); } } - diff --git a/src/transactionsOutcomeParsers/resources.ts b/src/transactionsOutcomeParsers/resources.ts new file mode 100644 index 00000000..4fd7249a --- /dev/null +++ b/src/transactionsOutcomeParsers/resources.ts @@ -0,0 +1,55 @@ +export class TransactionEvent { + address: string; + identifier: string; + topics: string[]; + data: Uint8Array; + + constructor(init: Partial) { + this.address = ""; + this.identifier = ""; + this.topics = []; + this.data = new Uint8Array(); + + Object.assign(this, init); + } +} + +export class TransactionLogs { + address: string; + events: TransactionEvent[]; + + constructor(init: Partial) { + this.address = ""; + this.events = []; + + Object.assign(this, init); + } +} + +export class SmartContractResult { + sender: string; + receiver: string; + data: Uint8Array; + logs: TransactionLogs; + + constructor(init: Partial) { + this.sender = ""; + this.receiver = ""; + this.data = new Uint8Array(); + this.logs = new TransactionLogs({}); + + Object.assign(this, init); + } +} + +export class TransactionOutcome { + smartContractResults: SmartContractResult[]; + transactionLogs: TransactionLogs; + + constructor(init: Partial) { + this.smartContractResults = []; + this.transactionLogs = new TransactionLogs({}); + + Object.assign(this, init); + } +} diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts new file mode 100644 index 00000000..976ea5a9 --- /dev/null +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts @@ -0,0 +1,560 @@ +import { assert } from "chai"; +import { ErrParseTransactionOutcome } from "../errors"; +import { TokenManagementTransactionsOutcomeParser } from "./tokenManagementTransactionsOutcomeParser"; +import { SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome } from "./resources"; + +describe("test token management transactions outcome parser", () => { + const parser = new TokenManagementTransactionsOutcomeParser(); + + it("should test ensure error", () => { + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "signalError", + topics: ["Avk0jZ1kR+l9c76wQQoYcu4hvXPz+jxxTdqQeaCrbX8=", "dGlja2VyIG5hbWUgaXMgbm90IHZhbGlk"], + data: Buffer.from("QDc1NzM2NTcyMjA2NTcyNzI2Zjcy", "base64"), + }); + + const logs = new TransactionLogs({ events: [event] }); + const scResult = new SmartContractResult({}); + const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + + assert.throws( + () => { + parser.parseIssueFungible(txOutcome); + }, + ErrParseTransactionOutcome, + "encountered signalError: ticker name is not valid (user error)", + ); + }); + + it("should test parse issue fungible", () => { + const identifer = "ZZZ-9ee87d"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issue", + topics: [base64Identifier, "U0VDT05E", "Wlpa", "RnVuZ2libGVFU0RU", "Ag=="], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + + const outcome = parser.parseIssueFungible(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + }); + + it("should test parse issue non fungible", () => { + const identifer = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + + const firstEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "upgradeProperties", + topics: ["TkZULWYwMWQxZQ==", "", "Y2FuVXBncmFkZQ==", "dHJ1ZQ==", "Y2FuQWRkU3BlY2lhbFJvbGVz", "dHJ1ZQ=="], + }); + + const secondEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetBurnRoleForAll", + topics: ["TkZULWYwMWQxZQ==", "", "", "RVNEVFJvbGVCdXJuRm9yQWxs"], + }); + + const thirdEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issueNonFungible", + topics: [base64Identifier, "TkZURVNU", "TkZU", "Tm9uRnVuZ2libGVFU0RU"], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [firstEvent, secondEvent, thirdEvent], + }); + + const scResult = new SmartContractResult({}); + const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + + const outcome = parser.parseIssueNonFungible(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + }); + + it("should test parse issue semi fungible", () => { + const identifer = "SEMIFNG-2c6d9f"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issueSemiFungible", + topics: [base64Identifier, "U0VNSQ==", "U0VNSUZORw==", "U2VtaUZ1bmdpYmxlRVNEVA=="], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + + const outcome = parser.parseIssueSemiFungible(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + }); + + it("should test parse register meta esdt", () => { + const identifer = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerMetaESDT", + topics: [base64Identifier, "TUVURVNU", "TUVUQVRFU1Q=", "TWV0YUVTRFQ="], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + + const outcome = parser.parseRegisterMetaEsdt(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + }); + + it("should test parse register and set all roles", () => { + const identifer = "LMAO-d9f892"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const roles = ["ESDTRoleLocalMint", "ESDTRoleLocalBurn"]; + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerAndSetAllRoles", + topics: [base64Identifier, "TE1BTw==", "TE1BTw==", "RnVuZ2libGVFU0RU", "Ag=="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const resultEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: ["TE1BTy1kOWY4OTI=", "", "", "RVNEVFJvbGVMb2NhbE1pbnQ=", "RVNEVFJvbGVMb2NhbEJ1cm4="], + }); + + const resultLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [resultEvent], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + data: Buffer.from( + "RVNEVFNldFJvbGVANGM0ZDQxNGYyZDY0Mzk2NjM4MzkzMkA0NTUzNDQ1NDUyNmY2YzY1NGM2ZjYzNjE2YzRkNjk2ZTc0QDQ1NTM0NDU0NTI2ZjZjNjU0YzZmNjM2MTZjNDI3NTcyNmU=", + "base64", + ), + logs: resultLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseRegisterAndSetAllRoles(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + assert.deepEqual(outcome.roles, roles); + }); + + it("should test parse register set special role", () => { + const identifer = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const roles = ["ESDTRoleNFTCreate", "ESDTRoleNFTAddQuantity", "ESDTRoleNFTBurn"]; + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: [ + base64Identifier, + "", + "", + "RVNEVFJvbGVORlRDcmVhdGU=", + "RVNEVFJvbGVORlRBZGRRdWFudGl0eQ==", + "RVNEVFJvbGVORlRCdXJu", + ], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseSetSpecialRole(txOutcome); + assert.equal(outcome.userAddress, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(outcome.tokenIdentifier, identifer); + assert.deepEqual(outcome.roles, roles); + }); + + it("should test parse nft create", () => { + const identifer = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(1); + const initialQuantity = BigInt(1); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTCreate", + topics: [ + base64Identifier, + "AQ==", + "AQ==", + "CAESAgABIuUBCAESCE5GVEZJUlNUGiA8NdfqyxqZpKDMqlN+8MwK4Qn0H2wrQCID5jO/uwcfXCDEEyouUW1ZM3ZKQ3NVcWpNM3hxeGR3VWczemJoVFNMUWZoN0szbW5aWXhyaGNRRFl4RzJDaHR0cHM6Ly9pcGZzLmlvL2lwZnMvUW1ZM3ZKQ3NVcWpNM3hxeGR3VWczemJoVFNMUWZoN0szbW5aWXhyaGNRRFl4Rzo9dGFnczo7bWV0YWRhdGE6UW1SY1A5NGtYcjV6WmpSR3ZpN21KNnVuN0xweFVoWVZSNFI0UnBpY3h6Z1lrdA==", + ], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseNftCreate(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.initialQuantity, initialQuantity); + }); + + it("should test parse local mint", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(0); + const mintedSupply = BigInt(100000); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTLocalMint", + topics: [base64Identifier, "", "AYag"], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseLocalMint(txOutcome); + assert.equal(outcome.userAddress, event.address); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.mintedSupply, mintedSupply); + }); + + it("should test parse local burn", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(0); + const burntSupply = BigInt(100000); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTLocalBurn", + topics: [base64Identifier, "", "AYag"], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseLocalBurn(txOutcome); + assert.equal(outcome.userAddress, event.address); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.burntSupply, burntSupply); + }); + + it("should test parse pause", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTPause", + topics: [base64Identifier], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parsePause(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + }); + + it("should test parse unpause", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTUnPause", + topics: [base64Identifier], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseUnpause(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + }); + + it("should test parse freeze", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTFreeze", + topics: [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseFreeze(txOutcome); + assert.equal(outcome.userAddress, address); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.balance, balance); + }); + + it("should test parse unfreeze", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTUnFreeze", + topics: [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseUnfreeze(txOutcome); + assert.equal(outcome.userAddress, address); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.balance, balance); + }); + + it("should test parse wipe", () => { + const identifer = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTWipe", + topics: [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseWipe(txOutcome); + assert.equal(outcome.userAddress, address); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.balance, balance); + }); + + it("should test parse update attributes", () => { + const identifer = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(1); + const attributes = "metadata:ipfsCID/test.json;tags:tag1,tag2"; + const base64Attributes = Buffer.from(attributes).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTUpdateAttributes", + topics: [base64Identifier, "AQ==", "", base64Attributes], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseUpdateAttributes(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(Buffer.from(outcome.attributes).toString(), attributes); + }); + + it("should test parse add quantity", () => { + const identifer = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(1); + const addedQuantity = BigInt(10); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTAddQuantity", + topics: [base64Identifier, "AQ==", "Cg=="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseAddQuantity(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.addedQuantity, addedQuantity); + }); + + it("should test parse burn quantity", () => { + const identifer = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifer).toString("base64"); + const nonce = BigInt(1); + const burntQuantity = BigInt(16); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTBurn", + topics: [base64Identifier, "AQ==", "EA=="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({}); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseBurnQuantity(txOutcome); + assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.burntQuantity, burntQuantity); + }); +}); diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts new file mode 100644 index 00000000..0c7ad943 --- /dev/null +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts @@ -0,0 +1,361 @@ +import { TransactionEvent, TransactionOutcome } from "./resources"; +import { ErrParseTransactionOutcome } from "../errors"; +import { Address } from "../address"; +import { bufferToBigInt } from "../smartcontracts/codec/utils"; + +export class TokenManagementTransactionsOutcomeParser { + constructor() {} + + parseIssueFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "issue"); + const identifer = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifer }; + } + + parseIssueNonFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "issueNonFungible"); + const identifer = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifer }; + } + + parseIssueSemiFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "issueSemiFungible"); + const identifer = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifer }; + } + + parseRegisterMetaEsdt(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "registerMetaESDT"); + const identifer = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifer }; + } + + parseRegisterAndSetAllRoles(transactionOutcome: TransactionOutcome): { tokenIdentifier: string; roles: string[] } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const registerEvent = this.findSingleEventByIdentifier(transactionOutcome, "registerAndSetAllRoles"); + const tokenIdentifier = this.extractTokenIdentifier(registerEvent); + + const setRoleEvent = this.findSingleEventByIdentifier(transactionOutcome, "ESDTSetRole"); + const encodedRoles = setRoleEvent.topics.slice(3); + + let roles: string[] = []; + for (const role of encodedRoles) { + const decodedRole = Buffer.from(role, "base64"); + roles = roles.concat(decodedRole.toString()); + } + + return { tokenIdentifier: tokenIdentifier, roles: roles }; + } + + parseSetBurnRoleGlobally(transactionOutcome: TransactionOutcome) { + this.ensureNoError(transactionOutcome.transactionLogs.events); + } + + parseUnsetBurnRoleGlobally(transactionOutcome: TransactionOutcome) { + this.ensureNoError(transactionOutcome.transactionLogs.events); + } + + parseSetSpecialRole(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + roles: string[]; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTSetRole"); + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + + const encodedRoles = event.topics.slice(3); + + let roles: string[] = []; + for (const role of encodedRoles) { + const decodedRole = Buffer.from(role, "base64"); + roles = roles.concat(decodedRole.toString()); + } + + return { userAddress: userAddress, tokenIdentifier: tokenIdentifier, roles: roles }; + } + + parseNftCreate(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + initialQuantity: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTCreate"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const amount = this.extractAmount(event); + + return { tokenIdentifier: tokenIdentifier, nonce: nonce, initialQuantity: amount }; + } + + parseLocalMint(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + mintedSupply: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTLocalMint"); + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const mintedSupply = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + mintedSupply: mintedSupply, + }; + } + + parseLocalBurn(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + burntSupply: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTLocalBurn"); + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const burntSupply = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + burntSupply: burntSupply, + }; + } + + parsePause(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTPause"); + const tokenIdentifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: tokenIdentifier }; + } + + parseUnpause(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTUnPause"); + const tokenIdentifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: tokenIdentifier }; + } + + parseFreeze(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTFreeze"); + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseUnfreeze(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTUnFreeze"); + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseWipe(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTWipe"); + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseUpdateAttributes(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + attributes: Uint8Array; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTUpdateAttributes"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const attributes = event.topics[3] ? Buffer.from(event.topics[3], "base64") : new Uint8Array(); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + attributes: attributes, + }; + } + + parseAddQuantity(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + addedQuantity: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTAddQuantity"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const addedQuantity = this.extractAmount(event); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + addedQuantity: addedQuantity, + }; + } + + parseBurnQuantity(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + burntQuantity: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTBurn"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const burntQuantity = this.extractAmount(event); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + burntQuantity: burntQuantity, + }; + } + + private ensureNoError(transactionEvents: TransactionEvent[]) { + for (const event of transactionEvents) { + if (event.identifier == "signalError") { + const data = Buffer.from(event.data.toString().slice(1)).toString(); + const message = Buffer.from(event.topics[1], "base64").toString(); + + throw new ErrParseTransactionOutcome( + `encountered signalError: ${message} (${Buffer.from(data, "hex").toString()})`, + ); + } + } + } + + private findSingleEventByIdentifier(transactionOutcome: TransactionOutcome, identifier: string): TransactionEvent { + const events = this.gatherAllEvents(transactionOutcome).filter((event) => event.identifier == identifier); + + if (events.length == 0) { + throw new ErrParseTransactionOutcome(`cannot find event of type ${identifier}`); + } + if (events.length > 1) { + throw new ErrParseTransactionOutcome(`more than one event of type ${identifier}`); + } + + return events[0]; + } + + private gatherAllEvents(transactionOutcome: TransactionOutcome): TransactionEvent[] { + const allEvents = []; + + allEvents.push(...transactionOutcome.transactionLogs.events); + + for (const item of transactionOutcome.smartContractResults) { + allEvents.push(...item.logs.events); + } + + return allEvents; + } + + private extractTokenIdentifier(event: TransactionEvent): string { + if (!event.topics[0]) { + return ""; + } + return Buffer.from(event.topics[0], "base64").toString(); + } + + private extractNonce(event: TransactionEvent): bigint { + if (!event.topics[1]) { + return BigInt(0); + } + const nonce = Buffer.from(event.topics[1], "base64"); + return BigInt(bufferToBigInt(nonce).toFixed(0)); + } + + private extractAmount(event: TransactionEvent): bigint { + if (!event.topics[2]) { + return BigInt(0); + } + const amount = Buffer.from(event.topics[2], "base64"); + return BigInt(bufferToBigInt(amount).toFixed(0)); + } + + private extractAddress(event: TransactionEvent): string { + if (!event.topics[3]) { + return ""; + } + const address = Buffer.from(event.topics[3], "base64"); + return Address.fromBuffer(address).bech32(); + } +} From e76a3c28de92b29d628c4efa3e88f06da0980d68 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 16 Feb 2024 12:29:16 +0200 Subject: [PATCH 2/4] fixes after review --- src/transaction.ts | 58 ++----- ...anagementTransactionsOutcomeParser.spec.ts | 144 +++++++----------- ...okenManagementTransactionsOutcomeParser.ts | 30 ++-- 3 files changed, 88 insertions(+), 144 deletions(-) diff --git a/src/transaction.ts b/src/transaction.ts index 9518e31a..746482b6 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -560,51 +560,25 @@ export class TransactionNext { /** * Creates a new Transaction object. */ - public constructor({ - sender, - receiver, - gasLimit, - chainID, - nonce, - value, - senderUsername, - receiverUsername, - gasPrice, - data, - version, - options, - guardian, - }: { - nonce?: BigNumber.Value; - value?: BigNumber.Value; - sender: string; - receiver: string; - senderUsername?: string; - receiverUsername?: string; - gasPrice?: BigNumber.Value; - gasLimit: BigNumber.Value; - data?: Uint8Array; - chainID: string; - version?: number; - options?: number; - guardian?: string; - }) { - this.nonce = nonce || 0; - this.value = value || new BigNumber(0); - this.sender = sender; - this.receiver = receiver; - this.senderUsername = senderUsername || ""; - this.receiverUsername = receiverUsername || ""; - this.gasPrice = gasPrice || new BigNumber(TRANSACTION_MIN_GAS_PRICE); - this.gasLimit = gasLimit; - this.data = data || new Uint8Array(); - this.chainID = chainID; - this.version = version || TRANSACTION_VERSION_DEFAULT; - this.options = options || TRANSACTION_OPTIONS_DEFAULT; - this.guardian = guardian || ""; + public constructor(init: Partial) { + this.nonce = 0; + this.value = new BigNumber(0); + this.sender = ""; + this.receiver = ""; + this.senderUsername = ""; + this.receiverUsername = ""; + this.gasPrice = new BigNumber(TRANSACTION_MIN_GAS_PRICE); + this.gasLimit = 0; + this.data = new Uint8Array(); + this.chainID = ""; + this.version = TRANSACTION_VERSION_DEFAULT; + this.options = TRANSACTION_OPTIONS_DEFAULT; + this.guardian = ""; this.signature = new Uint8Array(); this.guardianSignature = new Uint8Array(); + + Object.assign(this, init); } } diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts index 976ea5a9..a95813f8 100644 --- a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts @@ -15,8 +15,7 @@ describe("test token management transactions outcome parser", () => { }); const logs = new TransactionLogs({ events: [event] }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); assert.throws( () => { @@ -28,8 +27,8 @@ describe("test token management transactions outcome parser", () => { }); it("should test parse issue fungible", () => { - const identifer = "ZZZ-9ee87d"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "ZZZ-9ee87d"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const event = new TransactionEvent({ address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", @@ -42,16 +41,15 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); const outcome = parser.parseIssueFungible(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); }); it("should test parse issue non fungible", () => { - const identifer = "NFT-f01d1e"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const firstEvent = new TransactionEvent({ address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", @@ -76,16 +74,15 @@ describe("test token management transactions outcome parser", () => { events: [firstEvent, secondEvent, thirdEvent], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); const outcome = parser.parseIssueNonFungible(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); }); it("should test parse issue semi fungible", () => { - const identifer = "SEMIFNG-2c6d9f"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "SEMIFNG-2c6d9f"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const event = new TransactionEvent({ address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", @@ -98,16 +95,15 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); const outcome = parser.parseIssueSemiFungible(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); }); it("should test parse register meta esdt", () => { - const identifer = "METATEST-e05d11"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const event = new TransactionEvent({ address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", @@ -120,16 +116,15 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], transactionLogs: logs }); + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); const outcome = parser.parseRegisterMetaEsdt(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); }); it("should test parse register and set all roles", () => { - const identifer = "LMAO-d9f892"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "LMAO-d9f892"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const roles = ["ESDTRoleLocalMint", "ESDTRoleLocalBurn"]; const event = new TransactionEvent({ @@ -170,13 +165,13 @@ describe("test token management transactions outcome parser", () => { }); const outcome = parser.parseRegisterAndSetAllRoles(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.deepEqual(outcome.roles, roles); }); it("should test parse register set special role", () => { - const identifer = "METATEST-e05d11"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const roles = ["ESDTRoleNFTCreate", "ESDTRoleNFTAddQuantity", "ESDTRoleNFTBurn"]; const event = new TransactionEvent({ @@ -197,22 +192,19 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseSetSpecialRole(txOutcome); assert.equal(outcome.userAddress, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.deepEqual(outcome.roles, roles); }); it("should test parse nft create", () => { - const identifer = "NFT-f01d1e"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(1); const initialQuantity = BigInt(1); @@ -232,22 +224,19 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseNftCreate(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.initialQuantity, initialQuantity); }); it("should test parse local mint", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(0); const mintedSupply = BigInt(100000); @@ -262,23 +251,20 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseLocalMint(txOutcome); assert.equal(outcome.userAddress, event.address); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.mintedSupply, mintedSupply); }); it("should test parse local burn", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(0); const burntSupply = BigInt(100000); @@ -293,23 +279,20 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseLocalBurn(txOutcome); assert.equal(outcome.userAddress, event.address); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.burntSupply, burntSupply); }); it("should test parse pause", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const event = new TransactionEvent({ address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", @@ -322,20 +305,17 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parsePause(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); }); it("should test parse unpause", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const event = new TransactionEvent({ address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", @@ -348,20 +328,17 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseUnpause(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); }); it("should test parse freeze", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(0); const balance = BigInt(10000000); const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; @@ -390,14 +367,14 @@ describe("test token management transactions outcome parser", () => { const outcome = parser.parseFreeze(txOutcome); assert.equal(outcome.userAddress, address); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.balance, balance); }); it("should test parse unfreeze", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(0); const balance = BigInt(10000000); const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; @@ -426,14 +403,14 @@ describe("test token management transactions outcome parser", () => { const outcome = parser.parseUnfreeze(txOutcome); assert.equal(outcome.userAddress, address); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.balance, balance); }); it("should test parse wipe", () => { - const identifer = "AAA-29c4c9"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(0); const balance = BigInt(10000000); const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; @@ -462,14 +439,14 @@ describe("test token management transactions outcome parser", () => { const outcome = parser.parseWipe(txOutcome); assert.equal(outcome.userAddress, address); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.balance, balance); }); it("should test parse update attributes", () => { - const identifer = "NFT-f01d1e"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(1); const attributes = "metadata:ipfsCID/test.json;tags:tag1,tag2"; const base64Attributes = Buffer.from(attributes).toString("base64"); @@ -485,22 +462,19 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseUpdateAttributes(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(Buffer.from(outcome.attributes).toString(), attributes); }); it("should test parse add quantity", () => { - const identifer = "NFT-f01d1e"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(1); const addedQuantity = BigInt(10); @@ -515,22 +489,19 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseAddQuantity(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.addedQuantity, addedQuantity); }); it("should test parse burn quantity", () => { - const identifer = "NFT-f01d1e"; - const base64Identifier = Buffer.from(identifer).toString("base64"); + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); const nonce = BigInt(1); const burntQuantity = BigInt(16); @@ -545,15 +516,12 @@ describe("test token management transactions outcome parser", () => { events: [event], }); - const scResult = new SmartContractResult({}); - const txOutcome = new TransactionOutcome({ - smartContractResults: [scResult], transactionLogs: transactionLogs, }); const outcome = parser.parseBurnQuantity(txOutcome); - assert.equal(outcome.tokenIdentifier, identifer); + assert.equal(outcome.tokenIdentifier, identifier); assert.equal(outcome.nonce, nonce); assert.equal(outcome.burntQuantity, burntQuantity); }); diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts index 0c7ad943..f9b389eb 100644 --- a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts @@ -10,36 +10,36 @@ export class TokenManagementTransactionsOutcomeParser { this.ensureNoError(transactionOutcome.transactionLogs.events); const event = this.findSingleEventByIdentifier(transactionOutcome, "issue"); - const identifer = this.extractTokenIdentifier(event); + const identifier = this.extractTokenIdentifier(event); - return { tokenIdentifier: identifer }; + return { tokenIdentifier: identifier }; } parseIssueNonFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { this.ensureNoError(transactionOutcome.transactionLogs.events); const event = this.findSingleEventByIdentifier(transactionOutcome, "issueNonFungible"); - const identifer = this.extractTokenIdentifier(event); + const identifier = this.extractTokenIdentifier(event); - return { tokenIdentifier: identifer }; + return { tokenIdentifier: identifier }; } parseIssueSemiFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { this.ensureNoError(transactionOutcome.transactionLogs.events); const event = this.findSingleEventByIdentifier(transactionOutcome, "issueSemiFungible"); - const identifer = this.extractTokenIdentifier(event); + const identifier = this.extractTokenIdentifier(event); - return { tokenIdentifier: identifer }; + return { tokenIdentifier: identifier }; } parseRegisterMetaEsdt(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { this.ensureNoError(transactionOutcome.transactionLogs.events); const event = this.findSingleEventByIdentifier(transactionOutcome, "registerMetaESDT"); - const identifer = this.extractTokenIdentifier(event); + const identifier = this.extractTokenIdentifier(event); - return { tokenIdentifier: identifer }; + return { tokenIdentifier: identifier }; } parseRegisterAndSetAllRoles(transactionOutcome: TransactionOutcome): { tokenIdentifier: string; roles: string[] } { @@ -53,8 +53,7 @@ export class TokenManagementTransactionsOutcomeParser { let roles: string[] = []; for (const role of encodedRoles) { - const decodedRole = Buffer.from(role, "base64"); - roles = roles.concat(decodedRole.toString()); + roles = roles.concat(this.decodeTopicAsString(role)); } return { tokenIdentifier: tokenIdentifier, roles: roles }; @@ -83,8 +82,7 @@ export class TokenManagementTransactionsOutcomeParser { let roles: string[] = []; for (const role of encodedRoles) { - const decodedRole = Buffer.from(role, "base64"); - roles = roles.concat(decodedRole.toString()); + roles = roles.concat(this.decodeTopicAsString(role)); } return { userAddress: userAddress, tokenIdentifier: tokenIdentifier, roles: roles }; @@ -294,7 +292,7 @@ export class TokenManagementTransactionsOutcomeParser { for (const event of transactionEvents) { if (event.identifier == "signalError") { const data = Buffer.from(event.data.toString().slice(1)).toString(); - const message = Buffer.from(event.topics[1], "base64").toString(); + const message = this.decodeTopicAsString(event.topics[1]); throw new ErrParseTransactionOutcome( `encountered signalError: ${message} (${Buffer.from(data, "hex").toString()})`, @@ -332,7 +330,7 @@ export class TokenManagementTransactionsOutcomeParser { if (!event.topics[0]) { return ""; } - return Buffer.from(event.topics[0], "base64").toString(); + return this.decodeTopicAsString(event.topics[0]); } private extractNonce(event: TransactionEvent): bigint { @@ -358,4 +356,8 @@ export class TokenManagementTransactionsOutcomeParser { const address = Buffer.from(event.topics[3], "base64"); return Address.fromBuffer(address).bech32(); } + + private decodeTopicAsString(topic: string): string { + return Buffer.from(topic, "base64").toString(); + } } From 23a75ed007fc5d98f9506a6b96433a3c6ad2f4ec Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 20 Feb 2024 13:51:28 +0200 Subject: [PATCH 3/4] atempt to undo formatting --- src/transaction.ts | 1250 +++++++++++++++++++++----------------------- 1 file changed, 610 insertions(+), 640 deletions(-) diff --git a/src/transaction.ts b/src/transaction.ts index a9a026cf..7b552cac 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -4,20 +4,7 @@ import { Compatibility } from "./compatibility"; import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_VERSION_DEFAULT } from "./constants"; import * as errors from "./errors"; import { Hash } from "./hash"; -import { - IAddress, - IChainID, - IGasLimit, - IGasPrice, - INonce, - IPlainTransactionObject, - ISignature, - ITransactionNext, - ITransactionOptions, - ITransactionPayload, - ITransactionValue, - ITransactionVersion, -} from "./interface"; +import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, IPlainTransactionObject, ISignature, ITransactionNext, ITransactionOptions, ITransactionPayload, ITransactionValue, ITransactionVersion } from "./interface"; import { INetworkConfig } from "./interfaceOfNetwork"; import { TransactionOptions, TransactionVersion } from "./networkParams"; import { ProtoSerializer } from "./proto"; @@ -32,470 +19,462 @@ const TRANSACTION_HASH_LENGTH = 32; * An abstraction for creating, signing and broadcasting transactions. */ export class Transaction { - /** - * The nonce of the transaction (the account sequence number of the sender). - */ - private nonce: INonce; - - /** - * The value to transfer. - */ - private value: ITransactionValue; - - /** - * The address of the sender. - */ - private sender: IAddress; - - /** - * The address of the receiver. - */ - private readonly receiver: IAddress; - - /** - * The username of the sender. - */ - private senderUsername: string; - - /** - * The username of the receiver. - */ - private receiverUsername: string; - - /** - * The gas price to be used. - */ - private gasPrice: IGasPrice; - - /** - * The maximum amount of gas to be consumed when processing the transaction. - */ - private gasLimit: IGasLimit; - - /** - * The payload of the transaction. - */ - private readonly data: ITransactionPayload; - - /** - * The chain ID of the Network (e.g. "1" for Mainnet). - */ - private chainID: IChainID; - - /** - * The version, required by the Network in order to correctly interpret the contents of the transaction. - * @deprecated Use getVersion() and setVersion() instead. - */ - version: TransactionVersion; - - /** - * The options field, useful for describing different settings available for transactions - * @deprecated Use getOptions() and setOptions() instead. - */ - options: TransactionOptions; - - /** - * The address of the guardian. - */ - private guardian: IAddress; - - /** - * The signature. - */ - private signature: Buffer; - - /** - * The signature of the guardian. - */ - private guardianSignature: Buffer; - - /** - * The transaction hash, also used as a transaction identifier. - */ - private hash: TransactionHash; - - /** - * Creates a new Transaction object. - */ - public constructor({ - nonce, - value, - sender, - receiver, - senderUsername, - receiverUsername, - gasPrice, - gasLimit, - data, - chainID, - version, - options, - guardian, - }: { - nonce?: INonce; - value?: ITransactionValue; - sender: IAddress; - receiver: IAddress; - senderUsername?: string; - receiverUsername?: string; - gasPrice?: IGasPrice; - gasLimit: IGasLimit; - data?: ITransactionPayload; - chainID: IChainID; - version?: ITransactionVersion; - options?: ITransactionOptions; - guardian?: IAddress; - }) { - this.nonce = nonce || 0; - this.value = value ? new BigNumber(value.toString()).toFixed(0) : 0; - this.sender = sender; - this.receiver = receiver; - this.senderUsername = senderUsername || ""; - this.receiverUsername = receiverUsername || ""; - this.gasPrice = gasPrice || TRANSACTION_MIN_GAS_PRICE; - this.gasLimit = gasLimit; - this.data = data || new TransactionPayload(); - this.chainID = chainID; - this.version = version ? new TransactionVersion(version.valueOf()) : TransactionVersion.withDefaultVersion(); - this.options = options ? new TransactionOptions(options.valueOf()) : TransactionOptions.withDefaultOptions(); - this.guardian = guardian || Address.empty(); - - this.signature = Buffer.from([]); - this.guardianSignature = Buffer.from([]); - this.hash = TransactionHash.empty(); - } - - getNonce(): INonce { - return this.nonce; - } - - /** - * Sets the account sequence number of the sender. Must be done prior signing. - */ - setNonce(nonce: INonce) { - this.nonce = nonce; - } - - getValue(): ITransactionValue { - return this.value; - } - - setValue(value: ITransactionValue) { - this.value = value; - } - - getSender(): IAddress { - return this.sender; - } - - setSender(sender: IAddress) { - this.sender = sender; - } - - getReceiver(): IAddress { - return this.receiver; - } - - getSenderUsername(): string { - return this.senderUsername; - } - - setSenderUsername(senderUsername: string) { - this.senderUsername = senderUsername; - } - - getReceiverUsername(): string { - return this.receiverUsername; - } - - setReceiverUsername(receiverUsername: string) { - this.receiverUsername = receiverUsername; - } - - getGuardian(): IAddress { - return this.guardian; - } - - getGasPrice(): IGasPrice { - return this.gasPrice; - } - - setGasPrice(gasPrice: IGasPrice) { - this.gasPrice = gasPrice; - } - - getGasLimit(): IGasLimit { - return this.gasLimit; - } - - setGasLimit(gasLimit: IGasLimit) { - this.gasLimit = gasLimit; - } - - getData(): ITransactionPayload { - return this.data; - } - - getChainID(): IChainID { - return this.chainID; - } - - setChainID(chainID: IChainID) { - this.chainID = chainID; - } - - getVersion(): TransactionVersion { - return this.version; - } - - setVersion(version: ITransactionVersion) { - this.version = new TransactionVersion(version.valueOf()); - } - - getOptions(): TransactionOptions { - // Make sure that "sdk-core v12" is compatible (for a while) with (older) libraries that were previously setting the (soon to be private) "options" field directly, - // instead of using the "setOptions()" method. - const options = new TransactionOptions(this.options.valueOf()); - return options; - } - - setOptions(options: ITransactionOptions) { - this.options = new TransactionOptions(options.valueOf()); - } - - getSignature(): Buffer { - return this.signature; - } - - getGuardianSignature(): Buffer { - return this.guardianSignature; - } - - setGuardian(guardian: IAddress) { - this.guardian = guardian; - } - - getHash(): TransactionHash { - guardNotEmpty(this.hash, "hash"); - return this.hash; - } - - /** - * Serializes a transaction to a sequence of bytes, ready to be signed. - * This function is called internally by signers. - */ - serializeForSigning(): Buffer { - // TODO: for appropriate tx.version, interpret tx.options accordingly and sign using the content / data hash - let plain = this.toPlainObject(); - // Make sure we never sign the transaction with another signature set up (useful when using the same method for verification) - if (plain.signature) { - delete plain.signature; - } - - if (plain.guardianSignature) { - delete plain.guardianSignature; - } - - if (!plain.guardian) { - delete plain.guardian; - } - - let serialized = JSON.stringify(plain); - - return Buffer.from(serialized); - } - - /** - * Checks the integrity of the guarded transaction - */ - isGuardedTransaction(): boolean { - const hasGuardian = this.guardian.bech32().length > 0; - const hasGuardianSignature = this.guardianSignature.length > 0; - return this.getOptions().isWithGuardian() && hasGuardian && hasGuardianSignature; - } - - /** - * Converts the transaction object into a ready-to-serialize, plain JavaScript object. - * This function is called internally within the signing procedure. - */ - toPlainObject(): IPlainTransactionObject { - const plainObject = { - nonce: this.nonce.valueOf(), - value: this.value.toString(), - receiver: this.receiver.bech32(), - sender: this.sender.bech32(), - senderUsername: this.senderUsername ? Buffer.from(this.senderUsername).toString("base64") : undefined, - receiverUsername: this.receiverUsername ? Buffer.from(this.receiverUsername).toString("base64") : undefined, - gasPrice: this.gasPrice.valueOf(), - gasLimit: this.gasLimit.valueOf(), - data: this.data.length() == 0 ? undefined : this.data.encoded(), - chainID: this.chainID.valueOf(), - version: this.getVersion().valueOf(), - options: this.getOptions().valueOf() == 0 ? undefined : this.getOptions().valueOf(), - guardian: this.guardian?.bech32() - ? this.guardian.bech32() == "" - ? undefined - : this.guardian.bech32() - : undefined, - signature: this.signature.toString("hex") ? this.signature.toString("hex") : undefined, - guardianSignature: this.guardianSignature.toString("hex") - ? this.guardianSignature.toString("hex") - : undefined, - }; - - Compatibility.guardAddressIsSetAndNonZero( - new Address(plainObject.sender), - "'sender' of transaction", - "pass the actual sender to the Transaction constructor", - ); - - return plainObject; - } - - /** - * Converts a plain object transaction into a Transaction Object. - * - * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() - */ - static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { - const tx = new Transaction({ - nonce: Number(plainObjectTransaction.nonce), - value: new BigNumber(plainObjectTransaction.value).toFixed(0), - receiver: Address.fromString(plainObjectTransaction.receiver), - receiverUsername: plainObjectTransaction.receiverUsername - ? Buffer.from(plainObjectTransaction.receiverUsername, "base64").toString() - : undefined, - sender: Address.fromString(plainObjectTransaction.sender), - senderUsername: plainObjectTransaction.senderUsername - ? Buffer.from(plainObjectTransaction.senderUsername, "base64").toString() - : undefined, - guardian: plainObjectTransaction.guardian ? Address.fromString(plainObjectTransaction.guardian) : undefined, - gasPrice: Number(plainObjectTransaction.gasPrice), - gasLimit: Number(plainObjectTransaction.gasLimit), - data: new TransactionPayload(Buffer.from(plainObjectTransaction.data || "", "base64")), - chainID: String(plainObjectTransaction.chainID), - version: new TransactionVersion(plainObjectTransaction.version), - options: - plainObjectTransaction.options != null - ? new TransactionOptions(plainObjectTransaction.options) - : undefined, - }); - - if (plainObjectTransaction.signature) { - tx.applySignature(new Signature(plainObjectTransaction.signature)); - } - - if (plainObjectTransaction.guardianSignature) { - tx.applyGuardianSignature(new Signature(plainObjectTransaction.guardianSignature)); - } - - return tx; - } - - /** - * Applies the signature on the transaction. - * - * @param signature The signature, as computed by a signer. - */ - applySignature(signature: ISignature | Uint8Array) { - this.signature = interpretSignatureAsBuffer(signature); - this.hash = TransactionHash.compute(this); - } - - /** - * Applies the guardian signature on the transaction. - * - * @param guardianSignature The signature, as computed by a signer. - */ - applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { - this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); - this.hash = TransactionHash.compute(this); - } - - /** - * Converts a transaction to a ready-to-broadcast object. - * Called internally by the network provider. - */ - toSendable(): any { - return this.toPlainObject(); - } - - /** - * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties - * @param networkConfig {@link NetworkConfig} - */ - computeFee(networkConfig: INetworkConfig): BigNumber { - let moveBalanceGas = - networkConfig.MinGasLimit.valueOf() + this.data.length() * networkConfig.GasPerDataByte.valueOf(); - if (moveBalanceGas > this.gasLimit.valueOf()) { - throw new errors.ErrNotEnoughGas(this.gasLimit.valueOf()); - } - - let gasPrice = new BigNumber(this.gasPrice.valueOf()); - let feeForMove = new BigNumber(moveBalanceGas).multipliedBy(gasPrice); - if (moveBalanceGas === this.gasLimit.valueOf()) { - return feeForMove; - } - - let diff = new BigNumber(this.gasLimit.valueOf() - moveBalanceGas); - let modifiedGasPrice = gasPrice.multipliedBy(new BigNumber(networkConfig.GasPriceModifier.valueOf())); - let processingFee = diff.multipliedBy(modifiedGasPrice); - - return feeForMove.plus(processingFee); - } - - /** - * Creates a new Transaction object from a TransactionNext object. - */ - static fromTransactionNext(transaction: ITransactionNext): Transaction { - const tx = new Transaction({ - sender: Address.fromBech32(transaction.sender), - receiver: Address.fromBech32(transaction.receiver), - gasLimit: Number(transaction.gasLimit), - chainID: transaction.chainID, - value: new BigNumber(transaction.value.toString()).toFixed(0), - data: new TransactionPayload(Buffer.from(transaction.data)), - nonce: Number(transaction.nonce), - gasPrice: Number(transaction.gasPrice), - receiverUsername: transaction.receiverUsername, - senderUsername: transaction.senderUsername, - options: transaction.options, - version: transaction.version, - }); - - if (transaction.guardian) { - tx.guardian = Address.fromBech32(transaction.guardian); - } - - if (transaction.signature.length) { - tx.applySignature(transaction.signature); - } - - if (transaction.guardianSignature.length) { - tx.applyGuardianSignature(transaction.guardianSignature); - } - - return tx; - } + /** + * The nonce of the transaction (the account sequence number of the sender). + */ + private nonce: INonce; + + /** + * The value to transfer. + */ + private value: ITransactionValue; + + /** + * The address of the sender. + */ + private sender: IAddress; + + /** + * The address of the receiver. + */ + private readonly receiver: IAddress; + + /** + * The username of the sender. + */ + private senderUsername: string; + + /** + * The username of the receiver. + */ + private receiverUsername: string; + + /** + * The gas price to be used. + */ + private gasPrice: IGasPrice; + + /** + * The maximum amount of gas to be consumed when processing the transaction. + */ + private gasLimit: IGasLimit; + + /** + * The payload of the transaction. + */ + private readonly data: ITransactionPayload; + + /** + * The chain ID of the Network (e.g. "1" for Mainnet). + */ + private chainID: IChainID; + + /** + * The version, required by the Network in order to correctly interpret the contents of the transaction. + * @deprecated Use getVersion() and setVersion() instead. + */ + version: TransactionVersion; + + /** + * The options field, useful for describing different settings available for transactions + * @deprecated Use getOptions() and setOptions() instead. + */ + options: TransactionOptions; + + /** + * The address of the guardian. + */ + private guardian: IAddress; + + /** + * The signature. + */ + private signature: Buffer; + + /** + * The signature of the guardian. + */ + private guardianSignature: Buffer; + + /** + * The transaction hash, also used as a transaction identifier. + */ + private hash: TransactionHash; + + /** + * Creates a new Transaction object. + */ + public constructor({ + nonce, + value, + sender, + receiver, + senderUsername, + receiverUsername, + gasPrice, + gasLimit, + data, + chainID, + version, + options, + guardian, + }: { + nonce?: INonce; + value?: ITransactionValue; + sender: IAddress; + receiver: IAddress; + senderUsername?: string; + receiverUsername?: string; + gasPrice?: IGasPrice; + gasLimit: IGasLimit; + data?: ITransactionPayload; + chainID: IChainID; + version?: ITransactionVersion; + options?: ITransactionOptions; + guardian?: IAddress; + }) { + this.nonce = nonce || 0; + this.value = value ? new BigNumber(value.toString()).toFixed(0) : 0; + this.sender = sender; + this.receiver = receiver; + this.senderUsername = senderUsername || ""; + this.receiverUsername = receiverUsername || ""; + this.gasPrice = gasPrice || TRANSACTION_MIN_GAS_PRICE; + this.gasLimit = gasLimit; + this.data = data || new TransactionPayload(); + this.chainID = chainID; + this.version = version ? new TransactionVersion(version.valueOf()) : TransactionVersion.withDefaultVersion(); + this.options = options ? new TransactionOptions(options.valueOf()) : TransactionOptions.withDefaultOptions(); + this.guardian = guardian || Address.empty(); + + this.signature = Buffer.from([]); + this.guardianSignature = Buffer.from([]); + this.hash = TransactionHash.empty(); + } + + getNonce(): INonce { + return this.nonce; + } + + /** + * Sets the account sequence number of the sender. Must be done prior signing. + */ + setNonce(nonce: INonce) { + this.nonce = nonce; + } + + getValue(): ITransactionValue { + return this.value; + } + + setValue(value: ITransactionValue) { + this.value = value; + } + + getSender(): IAddress { + return this.sender; + } + + setSender(sender: IAddress) { + this.sender = sender; + } + + getReceiver(): IAddress { + return this.receiver; + } + + getSenderUsername(): string { + return this.senderUsername; + } + + setSenderUsername(senderUsername: string) { + this.senderUsername = senderUsername; + } + + getReceiverUsername(): string { + return this.receiverUsername; + } + + setReceiverUsername(receiverUsername: string) { + this.receiverUsername = receiverUsername; + } + + getGuardian(): IAddress { + return this.guardian; + } + + getGasPrice(): IGasPrice { + return this.gasPrice; + } + + setGasPrice(gasPrice: IGasPrice) { + this.gasPrice = gasPrice; + } + + getGasLimit(): IGasLimit { + return this.gasLimit; + } + + setGasLimit(gasLimit: IGasLimit) { + this.gasLimit = gasLimit; + } + + getData(): ITransactionPayload { + return this.data; + } + + getChainID(): IChainID { + return this.chainID; + } + + setChainID(chainID: IChainID) { + this.chainID = chainID; + } + + getVersion(): TransactionVersion { + return this.version; + } + + setVersion(version: ITransactionVersion) { + this.version = new TransactionVersion(version.valueOf()); + } + + getOptions(): TransactionOptions { + // Make sure that "sdk-core v12" is compatible (for a while) with (older) libraries that were previously setting the (soon to be private) "options" field directly, + // instead of using the "setOptions()" method. + const options = new TransactionOptions(this.options.valueOf()); + return options; + } + + setOptions(options: ITransactionOptions) { + this.options = new TransactionOptions(options.valueOf()); + } + + getSignature(): Buffer { + return this.signature; + } + + getGuardianSignature(): Buffer { + return this.guardianSignature; + } + + setGuardian(guardian: IAddress) { + this.guardian = guardian; + } + + getHash(): TransactionHash { + guardNotEmpty(this.hash, "hash"); + return this.hash; + } + + /** + * Serializes a transaction to a sequence of bytes, ready to be signed. + * This function is called internally by signers. + */ + serializeForSigning(): Buffer { + // TODO: for appropriate tx.version, interpret tx.options accordingly and sign using the content / data hash + let plain = this.toPlainObject(); + // Make sure we never sign the transaction with another signature set up (useful when using the same method for verification) + if (plain.signature) { + delete plain.signature; + } + + if (plain.guardianSignature) { + delete plain.guardianSignature; + } + + if (!plain.guardian) { + delete plain.guardian + } + + let serialized = JSON.stringify(plain); + + return Buffer.from(serialized); + } + + /** + * Checks the integrity of the guarded transaction + */ + isGuardedTransaction(): boolean { + const hasGuardian = this.guardian.bech32().length > 0; + const hasGuardianSignature = this.guardianSignature.length > 0; + return this.getOptions().isWithGuardian() && hasGuardian && hasGuardianSignature; + } + + /** + * Converts the transaction object into a ready-to-serialize, plain JavaScript object. + * This function is called internally within the signing procedure. + */ + toPlainObject(): IPlainTransactionObject { + const plainObject = { + nonce: this.nonce.valueOf(), + value: this.value.toString(), + receiver: this.receiver.bech32(), + sender: this.sender.bech32(), + senderUsername: this.senderUsername ? Buffer.from(this.senderUsername).toString("base64") : undefined, + receiverUsername: this.receiverUsername ? Buffer.from(this.receiverUsername).toString("base64") : undefined, + gasPrice: this.gasPrice.valueOf(), + gasLimit: this.gasLimit.valueOf(), + data: this.data.length() == 0 ? undefined : this.data.encoded(), + chainID: this.chainID.valueOf(), + version: this.getVersion().valueOf(), + options: this.getOptions().valueOf() == 0 ? undefined : this.getOptions().valueOf(), + guardian: this.guardian?.bech32() ? (this.guardian.bech32() == "" ? undefined : this.guardian.bech32()) : undefined, + signature: this.signature.toString("hex") ? this.signature.toString("hex") : undefined, + guardianSignature: this.guardianSignature.toString("hex") ? this.guardianSignature.toString("hex") : undefined, + }; + + Compatibility.guardAddressIsSetAndNonZero(new Address(plainObject.sender), "'sender' of transaction", "pass the actual sender to the Transaction constructor") + + return plainObject; + } + + /** + * Converts a plain object transaction into a Transaction Object. + * + * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() + */ + static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { + const tx = new Transaction({ + nonce: Number(plainObjectTransaction.nonce), + value: new BigNumber(plainObjectTransaction.value).toFixed(0), + receiver: Address.fromString(plainObjectTransaction.receiver), + receiverUsername: plainObjectTransaction.receiverUsername ? Buffer.from(plainObjectTransaction.receiverUsername, "base64").toString() : undefined, + sender: Address.fromString(plainObjectTransaction.sender), + senderUsername: plainObjectTransaction.senderUsername ? Buffer.from(plainObjectTransaction.senderUsername, "base64").toString() : undefined, + guardian: plainObjectTransaction.guardian ? Address.fromString(plainObjectTransaction.guardian) : undefined, + gasPrice: Number(plainObjectTransaction.gasPrice), + gasLimit: Number(plainObjectTransaction.gasLimit), + data: new TransactionPayload(Buffer.from(plainObjectTransaction.data || "", "base64")), + chainID: String(plainObjectTransaction.chainID), + version: new TransactionVersion(plainObjectTransaction.version), + options: plainObjectTransaction.options != null ? new TransactionOptions(plainObjectTransaction.options) : undefined + }); + + if (plainObjectTransaction.signature) { + tx.applySignature( + new Signature(plainObjectTransaction.signature), + ); + } + + if (plainObjectTransaction.guardianSignature) { + tx.applyGuardianSignature( + new Signature(plainObjectTransaction.guardianSignature) + ); + } + + return tx; + } + + /** + * Applies the signature on the transaction. + * + * @param signature The signature, as computed by a signer. + */ + applySignature(signature: ISignature | Uint8Array) { + this.signature = interpretSignatureAsBuffer(signature); + this.hash = TransactionHash.compute(this); + } + + /** + * Applies the guardian signature on the transaction. + * + * @param guardianSignature The signature, as computed by a signer. + */ + applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { + this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); + this.hash = TransactionHash.compute(this); + } + + /** + * Converts a transaction to a ready-to-broadcast object. + * Called internally by the network provider. + */ + toSendable(): any { + return this.toPlainObject(); + } + + /** + * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties + * @param networkConfig {@link NetworkConfig} + */ + computeFee(networkConfig: INetworkConfig): BigNumber { + let moveBalanceGas = + networkConfig.MinGasLimit.valueOf() + + this.data.length() * networkConfig.GasPerDataByte.valueOf(); + if (moveBalanceGas > this.gasLimit.valueOf()) { + throw new errors.ErrNotEnoughGas(this.gasLimit.valueOf()); + } + + let gasPrice = new BigNumber(this.gasPrice.valueOf()); + let feeForMove = new BigNumber(moveBalanceGas).multipliedBy(gasPrice); + if (moveBalanceGas === this.gasLimit.valueOf()) { + return feeForMove; + } + + let diff = new BigNumber(this.gasLimit.valueOf() - moveBalanceGas); + let modifiedGasPrice = gasPrice.multipliedBy( + new BigNumber(networkConfig.GasPriceModifier.valueOf()) + ); + let processingFee = diff.multipliedBy(modifiedGasPrice); + + return feeForMove.plus(processingFee); + } + + /** + * Creates a new Transaction object from a TransactionNext object. + */ + static fromTransactionNext(transaction: ITransactionNext): Transaction { + const tx = new Transaction({ + sender: Address.fromBech32(transaction.sender), + receiver: Address.fromBech32(transaction.receiver), + gasLimit: Number(transaction.gasLimit), + chainID: transaction.chainID, + value: new BigNumber(transaction.value.toString()).toFixed(0), + data: new TransactionPayload(Buffer.from(transaction.data)), + nonce: Number(transaction.nonce), + gasPrice: Number(transaction.gasPrice), + receiverUsername: transaction.receiverUsername, + senderUsername: transaction.senderUsername, + options: transaction.options, + version: transaction.version + }); + + if (transaction.guardian) { + tx.guardian = Address.fromBech32(transaction.guardian) + } + + if (transaction.signature.length) { + tx.applySignature(transaction.signature); + } + + if (transaction.guardianSignature.length) { + tx.applyGuardianSignature(transaction.guardianSignature); + } + + return tx; + } } /** * An abstraction for handling and computing transaction hashes. */ export class TransactionHash extends Hash { - constructor(hash: string) { - super(hash); - } - - /** - * Computes the hash of a transaction. - */ - static compute(transaction: Transaction): TransactionHash { - let serializer = new ProtoSerializer(); - let buffer = serializer.serializeTransaction(transaction); - let hash = createTransactionHasher(TRANSACTION_HASH_LENGTH).update(buffer).digest("hex"); - return new TransactionHash(hash); - } + constructor(hash: string) { + super(hash); + } + + /** + * Computes the hash of a transaction. + */ + static compute(transaction: Transaction): TransactionHash { + let serializer = new ProtoSerializer(); + let buffer = serializer.serializeTransaction(transaction); + let hash = createTransactionHasher(TRANSACTION_HASH_LENGTH) + .update(buffer) + .digest("hex"); + return new TransactionHash(hash); + } } /** @@ -503,103 +482,103 @@ export class TransactionHash extends Hash { * Will replace the {@link Transaction} class in the future. */ export class TransactionNext { - /** - * The nonce of the transaction (the account sequence number of the sender). - */ - public nonce: bigint; - - /** - * The value to transfer. - */ - public value: bigint; - - /** - * The address of the sender. - */ - public sender: string; - - /** - * The address of the receiver. - */ - public receiver: string; - - /** - * The username of the sender. - */ - public senderUsername: string; - - /** - * The username of the receiver. - */ - public receiverUsername: string; - - /** - * The gas price to be used. - */ - public gasPrice: bigint; - - /** - * The maximum amount of gas to be consumed when processing the transaction. - */ - public gasLimit: bigint; - - /** - * The payload of the transaction. - */ - public data: Uint8Array; - - /** - * The chain ID of the Network (e.g. "1" for Mainnet). - */ - public chainID: string; - - /** - * The version, required by the Network in order to correctly interpret the contents of the transaction. - */ - public version: number; - - /** - * The options field of the transactions. - */ - public options: number; - - /** - * The address of the guardian. - */ - public guardian: string; - - /** - * The signature. - */ - public signature: Uint8Array; - - /** - * The signature of the guardian. - */ - public guardianSignature: Uint8Array; - - /** - * Creates a new Transaction object. - */ + /** + * The nonce of the transaction (the account sequence number of the sender). + */ + public nonce: bigint; + + /** + * The value to transfer. + */ + public value: bigint; + + /** + * The address of the sender. + */ + public sender: string; + + /** + * The address of the receiver. + */ + public receiver: string; + + /** + * The username of the sender. + */ + public senderUsername: string; + + /** + * The username of the receiver. + */ + public receiverUsername: string; + + /** + * The gas price to be used. + */ + public gasPrice: bigint; + + /** + * The maximum amount of gas to be consumed when processing the transaction. + */ + public gasLimit: bigint; + + /** + * The payload of the transaction. + */ + public data: Uint8Array; + + /** + * The chain ID of the Network (e.g. "1" for Mainnet). + */ + public chainID: string; + + /** + * The version, required by the Network in order to correctly interpret the contents of the transaction. + */ + public version: number; + + /** + * The options field of the transactions. + */ + public options: number; + + /** + * The address of the guardian. + */ + public guardian: string; + + /** + * The signature. + */ + public signature: Uint8Array; + + /** + * The signature of the guardian. + */ + public guardianSignature: Uint8Array; + + /** + * Creates a new Transaction object. + */ public constructor(init: Partial) { - this.nonce = 0n; - this.value = 0n; - this.sender = ""; - this.receiver = ""; - this.senderUsername = ""; - this.receiverUsername = ""; - this.gasPrice = BigInt(TRANSACTION_MIN_GAS_PRICE); - this.gasLimit = 0n; - this.data = new Uint8Array(); - this.chainID = ""; - this.version = TRANSACTION_VERSION_DEFAULT; - this.options = TRANSACTION_OPTIONS_DEFAULT; - this.guardian = ""; - - this.signature = new Uint8Array(); - this.guardianSignature = new Uint8Array(); - - Object.assign(this, init); + this.nonce = 0n; + this.value = 0n; + this.sender = ""; + this.receiver = ""; + this.senderUsername = ""; + this.receiverUsername = ""; + this.gasPrice = BigInt(TRANSACTION_MIN_GAS_PRICE); + this.gasLimit = 0n; + this.data = new Uint8Array(); + this.chainID = ""; + this.version = TRANSACTION_VERSION_DEFAULT; + this.options = TRANSACTION_OPTIONS_DEFAULT; + this.guardian = ""; + + this.signature = new Uint8Array(); + this.guardianSignature = new Uint8Array(); + + Object.assign(this, init); } } @@ -607,89 +586,80 @@ export class TransactionNext { * An utilitary class meant to work together with the {@link TransactionNext} class. */ export class TransactionComputer { - constructor() {} - - computeTransactionFee(transaction: ITransactionNext, networkConfig: INetworkConfig): bigint { - const moveBalanceGas = BigInt( - networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte, - ); - if (moveBalanceGas > transaction.gasLimit) { - throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); - } - - const gasPrice = transaction.gasPrice; - const feeForMove = moveBalanceGas * gasPrice; - if (moveBalanceGas === transaction.gasLimit) { - return feeForMove; - } - - const diff = transaction.gasLimit - moveBalanceGas; - const modifiedGasPrice = BigInt( - new BigNumber(gasPrice.toString()).multipliedBy(new BigNumber(networkConfig.GasPriceModifier)).toFixed(0), - ); - const processingFee = diff * modifiedGasPrice; - - return feeForMove + processingFee; + constructor() { } + + computeTransactionFee(transaction: ITransactionNext, networkConfig: INetworkConfig): bigint { + const moveBalanceGas = BigInt( + networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte, + ); + if (moveBalanceGas > transaction.gasLimit) { + throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); } - computeBytesForSigning(transaction: ITransactionNext): Uint8Array { - const plainTransaction = this.toPlainObject(transaction); + const gasPrice = transaction.gasPrice; + const feeForMove = moveBalanceGas * gasPrice; + if (moveBalanceGas === transaction.gasLimit) { + return feeForMove; + } - if (plainTransaction.signature) { - delete plainTransaction.signature; - } + const diff = transaction.gasLimit - moveBalanceGas; + const modifiedGasPrice = BigInt( + new BigNumber(gasPrice.toString()).multipliedBy(new BigNumber(networkConfig.GasPriceModifier)).toFixed(0), + ); + const processingFee = diff * modifiedGasPrice; + + return feeForMove + processingFee; +} - if (plainTransaction.guardianSignature) { - delete plainTransaction.guardianSignature; - } + computeBytesForSigning(transaction: ITransactionNext): Uint8Array { + const plainTransaction = this.toPlainObject(transaction); - if (!plainTransaction.guardian) { - delete plainTransaction.guardian; - } + if (plainTransaction.signature) { + delete plainTransaction.signature; + } - const serialized = JSON.stringify(plainTransaction); + if (plainTransaction.guardianSignature) { + delete plainTransaction.guardianSignature; + } - return Buffer.from(serialized); + if (!plainTransaction.guardian) { + delete plainTransaction.guardian } - computeTransactionHash(transaction: ITransactionNext): Uint8Array { - let serializer = new ProtoSerializer(); + const serialized = JSON.stringify(plainTransaction); - const tx = Transaction.fromTransactionNext(transaction); - const buffer = serializer.serializeTransaction(tx); - const hash = createTransactionHasher(TRANSACTION_HASH_LENGTH).update(buffer).digest("hex"); + return new Uint8Array(Buffer.from(serialized)); + } - return Buffer.from(hash, "hex"); - } + computeTransactionHash(transaction: ITransactionNext): Uint8Array { + let serializer = new ProtoSerializer(); + + const tx = Transaction.fromTransactionNext(transaction); + const buffer = serializer.serializeTransaction(tx); + const hash = createTransactionHasher(TRANSACTION_HASH_LENGTH) + .update(buffer) + .digest("hex"); + + return Buffer.from(hash, "hex"); + } - private toPlainObject(transaction: ITransactionNext) { - return { - nonce: Number(transaction.nonce), - value: transaction.value.toString(), - receiver: transaction.receiver, - sender: transaction.sender, - senderUsername: transaction.senderUsername - ? Buffer.from(transaction.senderUsername).toString("base64") - : undefined, - receiverUsername: transaction.receiverUsername - ? Buffer.from(transaction.receiverUsername).toString("base64") - : undefined, - gasPrice: Number(transaction.gasPrice), - gasLimit: Number(transaction.gasLimit), - data: - transaction.data && transaction.data.length === 0 - ? undefined - : Buffer.from(transaction.data).toString("base64"), - chainID: transaction.chainID, - version: transaction.version, - options: transaction.options ? transaction.options : undefined, - guardian: transaction.guardian ? transaction.guardian : undefined, - signature: - transaction.signature.length == 0 ? undefined : Buffer.from(transaction.signature).toString("hex"), - guardianSignature: - transaction.guardianSignature.length == 0 - ? undefined - : Buffer.from(transaction.guardianSignature).toString("hex"), - }; + private toPlainObject(transaction: ITransactionNext) { + return { + nonce: Number(transaction.nonce), + value: transaction.value.toString(), + receiver: transaction.receiver, + sender: transaction.sender, + senderUsername: transaction.senderUsername ? Buffer.from(transaction.senderUsername).toString("base64") : undefined, + receiverUsername: transaction.receiverUsername ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined, + gasPrice: Number(transaction.gasPrice), + gasLimit: Number(transaction.gasLimit), + data: transaction.data && transaction.data.length === 0 ? undefined : Buffer.from(transaction.data).toString("base64"), + chainID: transaction.chainID, + version: transaction.version, + options: transaction.options ? transaction.options : undefined, + guardian: transaction.guardian ? transaction.guardian : undefined, + signature: transaction.signature.length == 0 ? undefined : Buffer.from(transaction.signature).toString("hex"), + guardianSignature: transaction.guardianSignature.length == 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex") } + } } From 15bf006373e996c684fac3a04136d05b3c3d3ee7 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 20 Feb 2024 13:55:23 +0200 Subject: [PATCH 4/4] fix indentation --- src/transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction.ts b/src/transaction.ts index 7b552cac..39303730 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -609,7 +609,7 @@ export class TransactionComputer { const processingFee = diff * modifiedGasPrice; return feeForMove + processingFee; -} + } computeBytesForSigning(transaction: ITransactionNext): Uint8Array { const plainTransaction = this.toPlainObject(transaction);