Skip to content

Commit

Permalink
Sync transactionEvent to specs
Browse files Browse the repository at this point in the history
  • Loading branch information
danielailie committed Nov 20, 2024
1 parent aa68ee4 commit 563b046
Show file tree
Hide file tree
Showing 29 changed files with 370 additions and 642 deletions.
85 changes: 39 additions & 46 deletions src/abi/resultsParser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import BigNumber from "bignumber.js";
import { assert } from "chai";
import { Address } from "../address";
import { IAddress } from "../interface";
import {
ContractQueryResponse,
ContractResultItem,
ContractResults,
TransactionEventData,
TransactionEventOnNetwork,
TransactionEventTopic,
TransactionLogsOnNetwork,
} from "../networkProviders";
import { loadAbiRegistry } from "../testutils";
import { ContractQueryResponse } from "../networkProviders";
import { b64TopicsToBytes, loadAbiRegistry } from "../testutils";
import { TransactionEvent } from "../transactionEvents";
import { TransactionLogs } from "../transactionLogs";
import { TransactionOnNetwork } from "../transactions";
import { SmartContractResult } from "../transactionsOutcomeParsers";
import { ArgSerializer } from "./argSerializer";
import { ResultsParser } from "./resultsParser";
import { ReturnCode } from "./returnCode";
Expand Down Expand Up @@ -186,7 +180,7 @@ describe("test smart contract results parser", () => {
let endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers);

let transactionOnNetwork = new TransactionOnNetwork({
contractResults: new ContractResults([new ContractResultItem({ nonce: 7, data: "@6f6b@2a@abba" })]),
smartContractResults: [new SmartContractResult({ data: Buffer.from("@6f6b@2a@abba") })],
});

let bundle = parser.parseOutcome(transactionOnNetwork, endpoint);
Expand All @@ -199,13 +193,11 @@ describe("test smart contract results parser", () => {

it("should parse contract outcome, on easily found result with return data", async () => {
let transaction = new TransactionOnNetwork({
contractResults: new ContractResults([
new ContractResultItem({
nonce: 42,
data: "@6f6b@03",
returnMessage: "foobar",
smartContractResults: [
new SmartContractResult({
data: Buffer.from("@6f6b@03"),
}),
]),
],
});

let bundle = parser.parseUntypedOutcome(transaction);
Expand All @@ -216,12 +208,12 @@ describe("test smart contract results parser", () => {

it("should parse contract outcome, on signal error", async () => {
let transaction = new TransactionOnNetwork({
logs: new TransactionLogsOnNetwork({
logs: new TransactionLogs({
address: Address.empty(),
events: [
new TransactionEventOnNetwork({
new TransactionEvent({
identifier: "signalError",
topics: [new TransactionEventTopic(Buffer.from("something happened").toString("base64"))],
topics: [Buffer.from("something happened")],
data: `@${Buffer.from("user error").toString("hex")}@07`,
}),
],
Expand All @@ -236,14 +228,15 @@ describe("test smart contract results parser", () => {

it("should parse contract outcome, on too much gas warning", async () => {
let transaction = new TransactionOnNetwork({
logs: new TransactionLogsOnNetwork({
logs: new TransactionLogs({
address: Address.empty(),
events: [
new TransactionEventOnNetwork({
new TransactionEvent({
identifier: "writeLog",
topics: [
new TransactionEventTopic(
Buffer.from(
"QHRvbyBtdWNoIGdhcyBwcm92aWRlZCBmb3IgcHJvY2Vzc2luZzogZ2FzIHByb3ZpZGVkID0gNTk2Mzg0NTAwLCBnYXMgdXNlZCA9IDczMzAxMA==",
"base64",
),
],
data: Buffer.from("QDZmNmI=", "base64").toString(),
Expand All @@ -262,19 +255,19 @@ describe("test smart contract results parser", () => {
const abiRegistry = await loadAbiRegistry("src/testdata/esdt-safe.abi.json");
const eventDefinition = abiRegistry.getEvent("deposit");

const event = new TransactionEventOnNetwork({
const event = new TransactionEvent({
topics: [
new TransactionEventTopic("ZGVwb3NpdA=="),
new TransactionEventTopic("cmzC1LRt1r10pMhNAnFb+FyudjGMq4G8CefCYdQUmmc="),
new TransactionEventTopic("AAAADFdFR0xELTAxZTQ5ZAAAAAAAAAAAAAAAAWQ="),
Buffer.from("ZGVwb3NpdA==", "base64"),
Buffer.from("cmzC1LRt1r10pMhNAnFb+FyudjGMq4G8CefCYdQUmmc=", "base64"),
Buffer.from("AAAADFdFR0xELTAxZTQ5ZAAAAAAAAAAAAAAAAWQ=", "base64"),
],
dataPayload: new TransactionEventData(Buffer.from("AAAAAAAAA9sAAAA=", "base64")),
dataPayload: Buffer.from("AAAAAAAAA9sAAAA=", "base64"),
});

const bundle = parser.parseEvent(event, eventDefinition);

assert.equal(
(<IAddress>bundle.dest_address).bech32(),
(<Address>bundle.dest_address).bech32(),
"erd1wfkv9495dhtt6a9yepxsyu2mlpw2ua333j4cr0qfulpxr4q5nfnshgyqun",
);
assert.equal(bundle.tokens[0].token_identifier, "WEGLD-01e49d");
Expand Down Expand Up @@ -315,16 +308,16 @@ describe("test smart contract results parser", () => {
const eventDefinition = abiRegistry.getEvent("foobar");

const event = {
topics: [
new TransactionEventTopic(Buffer.from("not used").toString("base64")),
new TransactionEventTopic(Buffer.from([42]).toString("base64")),
new TransactionEventTopic(Buffer.from("test").toString("base64")),
new TransactionEventTopic(Buffer.from([43]).toString("base64")),
new TransactionEventTopic(Buffer.from("test").toString("base64")),
new TransactionEventTopic(Buffer.from("test").toString("base64")),
new TransactionEventTopic(Buffer.from([44]).toString("base64")),
],
dataPayload: new TransactionEventData(Buffer.from([42])),
topics: b64TopicsToBytes([
Buffer.from("not used").toString("base64"),
Buffer.from([42]).toString("base64"),
Buffer.from("test").toString("base64"),
Buffer.from([43]).toString("base64"),
Buffer.from("test").toString("base64"),
Buffer.from("test").toString("base64"),
Buffer.from([44]).toString("base64"),
]),
dataPayload: Buffer.from([42]),
};

const bundle = parser.parseEvent(event, eventDefinition);
Expand Down Expand Up @@ -362,13 +355,13 @@ describe("test smart contract results parser", () => {
const eventDefinition = abiRegistry.getEvent("foobar");

const event = {
topics: [
new TransactionEventTopic(Buffer.from("not used").toString("base64")),
new TransactionEventTopic(Buffer.from([42]).toString("base64")),
],
additionalData: [new TransactionEventData(Buffer.from([43])), new TransactionEventData(Buffer.from([44]))],
topics: b64TopicsToBytes([
Buffer.from("not used").toString("base64"),
Buffer.from([42]).toString("base64"),
]),
additionalData: [Buffer.from([43]), Buffer.from([44])],
// Will be ignored.
dataPayload: new TransactionEventData(Buffer.from([43])),
dataPayload: Buffer.from([43]),
};

const bundle = parser.parseEvent(event, eventDefinition);
Expand Down
72 changes: 26 additions & 46 deletions src/abi/resultsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import {
} from "@multiversx/sdk-transaction-decoder/lib/src/transaction.decoder";
import { Address } from "../address";
import { ErrCannotParseContractResults } from "../errors";
import { IAddress } from "../interface";
import {
IContractQueryResponse,
IContractResults,
ITransactionLogs,
ITransactionOnNetwork,
} from "../interfaceOfNetwork";
import { IContractQueryResponse } from "../interfaceOfNetwork";
import { Logger } from "../logger";
import { TransactionLogs } from "../transactionLogs";
import { TransactionOnNetwork } from "../transactions";
import { SmartContractResult } from "../transactionsOutcomeParsers";
import { ArgSerializer } from "./argSerializer";
import { TypedOutcomeBundle, UntypedOutcomeBundle } from "./interface";
import { ReturnCode } from "./returnCode";
Expand Down Expand Up @@ -113,7 +110,7 @@ export class ResultsParser {
/**
* Legacy method, use "SmartContractTransactionsOutcomeParser.parseExecute()" instead.
*/
parseOutcome(transaction: ITransactionOnNetwork, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle {
parseOutcome(transaction: TransactionOnNetwork, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle {
const untypedBundle = this.parseUntypedOutcome(transaction);
const typedBundle = this.parseOutcomeFromUntypedBundle(untypedBundle, endpoint);
return typedBundle;
Expand All @@ -140,7 +137,7 @@ export class ResultsParser {
/**
* Legacy method, use "SmartContractTransactionsOutcomeParser.parseExecute()" instead.
*/
parseUntypedOutcome(transaction: ITransactionOnNetwork): UntypedOutcomeBundle {
parseUntypedOutcome(transaction: TransactionOnNetwork): UntypedOutcomeBundle {
let bundle: UntypedOutcomeBundle | null;

let transactionMetadata = this.parseTransactionMetadata(transaction);
Expand All @@ -151,13 +148,12 @@ export class ResultsParser {
return bundle;
}

bundle = this.createBundleOnInvalidTransaction(transaction);
if (bundle) {
Logger.trace("parseUntypedOutcome(): on invalid transaction");
return bundle;
}

bundle = this.createBundleOnEasilyFoundResultWithReturnData(transaction.contractResults);
bundle = this.createBundleOnEasilyFoundResultWithReturnData(transaction.smartContractResults);
if (bundle) {
Logger.trace("parseUntypedOutcome(): on easily found result with return data");
return bundle;
Expand Down Expand Up @@ -196,7 +192,7 @@ export class ResultsParser {
throw new ErrCannotParseContractResults(`transaction ${transaction.hash.toString()}`);
}

protected parseTransactionMetadata(transaction: ITransactionOnNetwork): TransactionMetadata {
protected parseTransactionMetadata(transaction: TransactionOnNetwork): TransactionMetadata {
return new TransactionDecoder().getTransactionMetadata({
sender: transaction.sender.bech32(),
receiver: transaction.receiver.bech32(),
Expand All @@ -205,8 +201,8 @@ export class ResultsParser {
});
}

protected createBundleOnSimpleMoveBalance(transaction: ITransactionOnNetwork): UntypedOutcomeBundle | null {
let noResults = transaction.contractResults.items.length == 0;
protected createBundleOnSimpleMoveBalance(transaction: TransactionOnNetwork): UntypedOutcomeBundle | null {
let noResults = transaction.smartContractResults.length == 0;
let noLogs = transaction.logs.events.length == 0;

if (noResults && noLogs) {
Expand All @@ -220,32 +216,16 @@ export class ResultsParser {
return null;
}

protected createBundleOnInvalidTransaction(transaction: ITransactionOnNetwork): UntypedOutcomeBundle | null {
if (transaction.status.isInvalid()) {
if (transaction.receipt.data) {
return {
returnCode: ReturnCode.OutOfFunds,
returnMessage: transaction.receipt.data,
values: [],
};
}

// If there's no receipt message, let other heuristics to handle the outcome (most probably, a log with "signalError" is emitted).
}

return null;
}

protected createBundleOnEasilyFoundResultWithReturnData(results: IContractResults): UntypedOutcomeBundle | null {
let resultItemWithReturnData = results.items.find(
(item) => item.nonce.valueOf() != 0 && item.data.startsWith("@"),
);
protected createBundleOnEasilyFoundResultWithReturnData(
results: SmartContractResult[],
): UntypedOutcomeBundle | null {
let resultItemWithReturnData = results.find((item) => item.data.toString().startsWith("@"));
if (!resultItemWithReturnData) {
return null;
}

let { returnCode, returnDataParts } = this.sliceDataFieldInParts(resultItemWithReturnData.data);
let returnMessage = resultItemWithReturnData.returnMessage || returnCode.toString();
let { returnCode, returnDataParts } = this.sliceDataFieldInParts(resultItemWithReturnData.data.toString());
let returnMessage = resultItemWithReturnData.raw["prevTxHash"] || returnCode.toString();

return {
returnCode: returnCode,
Expand All @@ -254,7 +234,7 @@ export class ResultsParser {
};
}

protected createBundleOnSignalError(logs: ITransactionLogs): UntypedOutcomeBundle | null {
protected createBundleOnSignalError(logs: TransactionLogs): UntypedOutcomeBundle | null {
let eventSignalError = logs.findSingleOrNoneEvent(WellKnownEvents.OnSignalError);
if (!eventSignalError) {
return null;
Expand All @@ -271,7 +251,7 @@ export class ResultsParser {
};
}

protected createBundleOnTooMuchGasWarning(logs: ITransactionLogs): UntypedOutcomeBundle | null {
protected createBundleOnTooMuchGasWarning(logs: TransactionLogs): UntypedOutcomeBundle | null {
let eventTooMuchGas = logs.findSingleOrNoneEvent(
WellKnownEvents.OnWriteLog,
(event) =>
Expand All @@ -293,14 +273,14 @@ export class ResultsParser {
}

protected createBundleOnWriteLogWhereFirstTopicEqualsAddress(
logs: ITransactionLogs,
address: IAddress,
logs: TransactionLogs,
address: Address,
): UntypedOutcomeBundle | null {
let hexAddress = new Address(address.bech32()).hex();

let eventWriteLogWhereTopicIsSender = logs.findSingleOrNoneEvent(
WellKnownEvents.OnWriteLog,
(event) => event.findFirstOrNoneTopic((topic) => topic.hex() == hexAddress) != undefined,
(event) => event.findFirstOrNoneTopic((topic) => topic.toString() == hexAddress) != undefined,
);

if (!eventWriteLogWhereTopicIsSender) {
Expand All @@ -321,23 +301,23 @@ export class ResultsParser {
* Override this method (in a subclass of {@link ResultsParser}) if the basic heuristics of the parser are not sufficient.
*/
protected createBundleWithCustomHeuristics(
_transaction: ITransactionOnNetwork,
_transaction: TransactionOnNetwork,
_transactionMetadata: TransactionMetadata,
): UntypedOutcomeBundle | null {
return null;
}

protected createBundleWithFallbackHeuristics(
transaction: ITransactionOnNetwork,
transaction: TransactionOnNetwork,
transactionMetadata: TransactionMetadata,
): UntypedOutcomeBundle | null {
let contractAddress = new Address(transactionMetadata.receiver);

// Search the nested logs for matching events (writeLog):
for (const resultItem of transaction.contractResults.items) {
for (const resultItem of transaction.smartContractResults) {
let writeLogWithReturnData = resultItem.logs.findSingleOrNoneEvent(WellKnownEvents.OnWriteLog, (event) => {
let addressIsSender = event.address.bech32() == transaction.sender.bech32();
let firstTopicIsContract = event.topics[0]?.hex() == contractAddress.hex();
let firstTopicIsContract = event.topics[0].toString() == contractAddress.hex();
return addressIsSender && firstTopicIsContract;
});

Expand All @@ -354,7 +334,7 @@ export class ResultsParser {
}

// Additional fallback heuristics (alter search constraints):
for (const resultItem of transaction.contractResults.items) {
for (const resultItem of transaction.smartContractResults) {
let writeLogWithReturnData = resultItem.logs.findSingleOrNoneEvent(WellKnownEvents.OnWriteLog, (event) => {
const addressIsContract = event.address.bech32() == contractAddress.toBech32();
return addressIsContract;
Expand Down
2 changes: 1 addition & 1 deletion src/abi/smartContract.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TransactionStatus } from "../networkProviders";
import { assert } from "chai";
import { Address } from "../address";
import {
Expand All @@ -9,6 +8,7 @@ import {
TestWallet,
Wait,
} from "../testutils";
import { TransactionStatus } from "../transactionStatus";
import { TransactionWatcher } from "../transactionWatcher";
import { Code } from "./code";
import { ContractFunction } from "./function";
Expand Down
Loading

0 comments on commit 563b046

Please sign in to comment.