Skip to content

Commit

Permalink
In outcome parsers, handle both TransactionOnNetwork and TransactionO…
Browse files Browse the repository at this point in the history
…utcome.
  • Loading branch information
andreibancioiu committed Oct 8, 2024
1 parent 52f0efb commit 475b8a4
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 78 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 27,5 @@ jobs:
- run: npm run lint
- run: npm run compile
- run: npm install esmify && npm run compile-browser
- run: npm test
- run: npm run tests-unit
- run: npm run tests-devnet
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 22,11 @@
],
"scripts": {
"test": "npm run tests-unit",
"tests-unit": "mocha $(find . -name '*.spec.ts' ! -name '*.local.net.spec.*' ! -name '*.devnet.spec.*' ! -name '*.testnet.spec.*')",
"tests-unit": "mocha $(find . -name '*.spec.ts' ! -name '*.local.net.spec.*' ! -name '*.test.net.spec.*' ! -name '*.dev.net.spec.*' ! -name '*.main.net.spec.*')",
"tests-localnet": "mocha $(find . -name '*.local.net.spec.ts')",
"tests-devnet": "mocha $(find . -name '*.devnet.spec.ts')",
"tests-testnet": "mocha $(find . -name '*.testnet.spec.ts')",
"tests-testnet": "mocha $(find . -name '*.test.net.spec.ts')",
"tests-devnet": "mocha $(find . -name '*.dev.net.spec.ts')",
"tests-mainnet": "mocha $(find . -name '*.main.net.spec.ts')",
"compile-browser": "tsc -p tsconfig.json && browserify out/index.js -o out-browser/sdk-core.js --standalone multiversxSdkCore -p esmify",
"compile": "tsc -p tsconfig.json",
"compile-proto": "npx pbjs -t static-module -w default -o src/proto/compiled.js src/proto/transaction.proto",
Expand Down
8 changes: 8 additions & 0 deletions src/converters/transactionsConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 79,14 @@ export class TransactionsConverter {
return Buffer.from(value || "", "hex");
}

/**
* @deprecated Where {@link TransactionOutcome} was needed (throughout the SDK), pass the {@link ITransactionOnNetwork} object instead.
*
* Summarizes the outcome of a transaction on the network, and maps it to the "standard" resources (according to the sdk-specs).
*
* In the future, this converter function will become obsolete,
* as the impedance mismatch between the network components and the "core" components will be reduced.
*/
public transactionOnNetworkToOutcome(transactionOnNetwork: ITransactionOnNetwork): TransactionOutcome {
// In the future, this will not be needed because the transaction, as returned from the API,
// will hold the data corresponding to the direct smart contract call outcome (in case of smart contract calls).
Expand Down
21 changes: 19 additions & 2 deletions src/testutils/networkProviders.ts
Original file line number Diff line number Diff line change
@@ -1,4 1,3 @@
import { ApiNetworkProvider, ProxyNetworkProvider } from "../networkProviders";
import { IAddress } from "../interface";
import {
IAccountOnNetwork,
Expand All @@ -7,6 6,7 @@ import {
ITransactionOnNetwork,
ITransactionStatus,
} from "../interfaceOfNetwork";
import { ApiNetworkProvider, ProxyNetworkProvider } from "../networkProviders";
import { Query } from "../smartcontracts/query";
import { Transaction } from "../transaction";

Expand All @@ -15,7 15,24 @@ export function createLocalnetProvider(): INetworkProvider {
}

export function createTestnetProvider(): INetworkProvider {
return new ApiNetworkProvider("https://testnet-api.multiversx.com", { timeout: 5000 });
return new ApiNetworkProvider("https://testnet-api.multiversx.com", {
timeout: 5000,
clientName: "mx-sdk-js-core/tests",
});
}

export function createDevnetProvider(): INetworkProvider {
return new ProxyNetworkProvider("https://devnet-gateway.multiversx.com", {
timeout: 5000,
clientName: "mx-sdk-js-core/tests",
});
}

export function createMainnetProvider(): INetworkProvider {
return new ProxyNetworkProvider("https://gateway.multiversx.com", {
timeout: 10000,
clientName: "mx-sdk-js-core/tests",
});
}

export interface INetworkProvider {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 1,35 @@
import { Address } from "../address";
import { TransactionsConverter } from "../converters/transactionsConverter";
import { ErrParseTransactionOutcome } from "../errors";
import { ITransactionOnNetwork } from "../interfaceOfNetwork";
import { TransactionEvent, TransactionOutcome, findEventsByIdentifier } from "./resources";

export class DelegationTransactionsOutcomeParser {
constructor() {}

parseCreateNewDelegationContract(transactionOutcome: TransactionOutcome): { contractAddress: string }[] {
this.ensureNoError(transactionOutcome.logs.events);
parseCreateNewDelegationContract(
transaction: TransactionOutcome | ITransactionOnNetwork,
): { contractAddress: string }[] {
transaction = this.ensureTransactionOutcome(transaction);

const events = findEventsByIdentifier(transactionOutcome, "SCDeploy");
this.ensureNoError(transaction.logs.events);

const events = findEventsByIdentifier(transaction, "SCDeploy");

return events.map((event) => ({ contractAddress: this.extractContractAddress(event) }));
}

/**
* Temporary workaround, until "TransactionOnNetwork" completely replaces "TransactionOutcome".
*/
private ensureTransactionOutcome(transaction: TransactionOutcome | ITransactionOnNetwork): TransactionOutcome {
if ("hash" in transaction) {
return new TransactionsConverter().transactionOnNetworkToOutcome(transaction);
}

return transaction;
}

private ensureNoError(transactionEvents: TransactionEvent[]) {
for (const event of transactionEvents) {
if (event.identifier == "signalError") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 1,40 @@
import { assert } from "chai";
import { TransactionsConverter } from "../converters/transactionsConverter";
import { createDevnetProvider } from "../testutils/networkProviders";
import { SmartContractTransactionsOutcomeParser } from "./smartContractTransactionsOutcomeParser";

describe("test smart contract transactions outcome parser on devnet", () => {
const networkProvider = createDevnetProvider();
const parser = new SmartContractTransactionsOutcomeParser();
const transactionsConverter = new TransactionsConverter();

it("should parse outcome of deploy transactions (1)", async () => {
const transactionHash = "5d2ff2af8eb3fe7f2acb7e29c0436854b4c6c44de02878b6afff582888024a55";
const transactionOnNetwork = await networkProvider.getTransaction(transactionHash);
const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork);
const parsedGivenTransactionOnNetwork = parser.parseDeploy({ transactionOnNetwork });
const parsedGivenTransactionOutcome = parser.parseDeploy({ transactionOutcome });

assert.deepEqual(parsedGivenTransactionOnNetwork, parsedGivenTransactionOutcome);
assert.equal(parsedGivenTransactionOnNetwork.returnCode, "ok");
assert.deepEqual(parsedGivenTransactionOnNetwork.contracts, [
{
address: "erd1qqqqqqqqqqqqqpgqpayq2es08gq8798xhnpr0kzgn7495qt5q6uqd7lpwf",
ownerAddress: "erd1tn62hjp72rznp8vq0lplva5csav6rccpqqdungpxtqz0g2hcq6uq9k4cc6",
codeHash: Buffer.from("c876625ec34a04445cfd99067777ebe488afdbc6899cd958f4c1d36107ca02d9", "hex"),
},
]);
});

it("should parse outcome of deploy transactions (2)", async () => {
const transactionHash = "76683e926dad142fc9651afca208487f2a80d327fc87e5c876eec9d028196352";
const transactionOnNetwork = await networkProvider.getTransaction(transactionHash);
const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork);
const parsedGivenTransactionOnNetwork = parser.parseDeploy({ transactionOnNetwork });
const parsedGivenTransactionOutcome = parser.parseDeploy({ transactionOutcome });

assert.deepEqual(parsedGivenTransactionOnNetwork, parsedGivenTransactionOutcome);
assert.equal(parsedGivenTransactionOnNetwork.returnCode, "execution failed");
assert.lengthOf(parsedGivenTransactionOnNetwork.contracts, 0);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 1,14 @@
import { Address } from "../address";
import { Err } from "../errors";
import { ITransactionOnNetwork } from "../interfaceOfNetwork";
import { EndpointDefinition, ResultsParser, ReturnCode, Type, UntypedOutcomeBundle } from "../smartcontracts";
import { TransactionEvent, TransactionOutcome, findEventsByIdentifier } from "./resources";
import { TransactionOutcome, findEventsByIdentifier } from "./resources";

enum Events {
SCDeploy = "SCDeploy",
SignalError = "signalError",
WriteLog = "writeLog",
}

interface IAbi {
getEndpoint(name: string): EndpointDefinition;
Expand All @@ -28,15 35,31 @@ export class SmartContractTransactionsOutcomeParser {

constructor(options?: { abi?: IAbi; legacyResultsParser?: ILegacyResultsParser }) {
this.abi = options?.abi;

// Prior v13, we've advertised that people can override the "ResultsParser" to alter it's behavior in case of exotic flows.
// Now, since the new "SmartContractTransactionsOutcomeParser" (still) depends on the legacy "ResultsParser",
// at least until "return data parts of direct outcome of contract call" are included on API & Proxy responses (on GET transaction),
// we have to allow the same level of customization (for exotic flows).
this.legacyResultsParser = options?.legacyResultsParser || new ResultsParser();
}

parseDeploy(options: { transactionOutcome: TransactionOutcome }): {
parseDeploy(
options: { transactionOutcome: TransactionOutcome } | { transactionOnNetwork: ITransactionOnNetwork },
): {
returnCode: string;
returnMessage: string;
contracts: {
address: string;
ownerAddress: string;
codeHash: Uint8Array;
}[];
} {
if ("transactionOutcome" in options) {
return this.parseDeployGivenTransactionOutcome(options.transactionOutcome);
}

return this.parseDeployGivenTransactionOnNetwork(options.transactionOnNetwork);
}

/**
* Legacy approach.
*/
protected parseDeployGivenTransactionOutcome(transactionOutcome: TransactionOutcome): {
returnCode: string;
returnMessage: string;
contracts: {
Expand All @@ -45,8 68,8 @@ export class SmartContractTransactionsOutcomeParser {
codeHash: Uint8Array;
}[];
} {
const directCallOutcome = options.transactionOutcome.directSmartContractCallOutcome;
const events = findEventsByIdentifier(options.transactionOutcome, "SCDeploy");
const directCallOutcome = transactionOutcome.directSmartContractCallOutcome;
const events = findEventsByIdentifier(transactionOutcome, Events.SCDeploy);
const contracts = events.map((event) => this.parseScDeployEvent(event));

return {
Expand All @@ -56,7 79,19 @@ export class SmartContractTransactionsOutcomeParser {
};
}

private parseScDeployEvent(event: TransactionEvent): {
protected parseDeployGivenTransactionOnNetwork(_transactionOnNetwork: ITransactionOnNetwork): {
returnCode: string;
returnMessage: string;
contracts: {
address: string;
ownerAddress: string;
codeHash: Uint8Array;
}[];
} {
throw new Error("Not implemented.");
}

private parseScDeployEvent(event: { topics: Uint8Array[] }): {
address: string;
ownerAddress: string;
codeHash: Uint8Array;
Expand All @@ -76,12 111,34 @@ export class SmartContractTransactionsOutcomeParser {
};
}

parseExecute(options: { transactionOutcome: TransactionOutcome; function?: string }): {
parseExecute(
options:
| { transactionOutcome: TransactionOutcome; function?: string }
| { transactionOnNetwork: ITransactionOnNetwork; function?: string },
): {
values: any[];
returnCode: string;
returnMessage: string;
} {
const directCallOutcome = options.transactionOutcome.directSmartContractCallOutcome;
if ("transactionOutcome" in options) {
return this.parseExecuteGivenTransactionOutcome(options.transactionOutcome, options.function);
}

return this.parseExecuteGivenTransactionOnNetwork(options.transactionOnNetwork, options.function);
}

/**
* Legacy approach.
*/
protected parseExecuteGivenTransactionOutcome(
transactionOutcome: TransactionOutcome,
functionName?: string,
): {
values: any[];
returnCode: string;
returnMessage: string;
} {
const directCallOutcome = transactionOutcome.directSmartContractCallOutcome;

if (!this.abi) {
return {
Expand All @@ -91,7 148,7 @@ export class SmartContractTransactionsOutcomeParser {
};
}

const functionName = options.function || directCallOutcome.function;
functionName = functionName || directCallOutcome.function;

if (!functionName) {
throw new Err(
Expand All @@ -115,4 172,15 @@ export class SmartContractTransactionsOutcomeParser {
returnMessage: legacyTypedBundle.returnMessage,
};
}

protected parseExecuteGivenTransactionOnNetwork(
_transactionOnNetwork: ITransactionOnNetwork,
_functionName?: string,
): {
values: any[];
returnCode: string;
returnMessage: string;
} {
throw new Error("Not implemented.");
}
}
Loading

0 comments on commit 475b8a4

Please sign in to comment.