Skip to content

Commit

Permalink
Merge pull request #424 from multiversx/parse-deploy
Browse files Browse the repository at this point in the history
SmartContractTransactionsOutcomeParser - parseDeploy()
  • Loading branch information
andreibancioiu authored Mar 29, 2024
2 parents 6549d63 + 0932d36 commit 900a8b8
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 22 deletions.
11 changes: 7 additions & 4 deletions src/address.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ describe("test address", () => {
let bobHex = "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8";

it("should create address", async () => {
let alice = new Address(aliceBech32);
let bob = new Address(bobBech32);
assert.equal(new Address(aliceBech32).toHex(), aliceHex);
assert.equal(new Address(bobBech32).toHex(), bobHex);

assert.equal(new Address(Buffer.from(aliceHex, "hex")).toHex(), aliceHex);
assert.equal(new Address(Buffer.from(bobHex, "hex")).toHex(), bobHex);

assert.equal(alice.hex(), aliceHex);
assert.equal(bob.hex(), bobHex);
assert.equal(new Address(new Uint8Array(Buffer.from(aliceHex, "hex"))).toHex(), aliceHex);
assert.equal(new Address(new Uint8Array(Buffer.from(bobHex, "hex"))).toHex(), bobHex);
});

it("should create empty address", async () => {
Expand Down
10 changes: 5 additions & 5 deletions src/address.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as bech32 from "bech32";
import * as errors from "./errors";
import { CURRENT_NUMBER_OF_SHARDS_WITHOUT_META, METACHAIN_ID, WasmVirtualMachine } from "./constants";
import BigNumber from "bignumber.js";
import { CURRENT_NUMBER_OF_SHARDS_WITHOUT_META, METACHAIN_ID, WasmVirtualMachine } from "./constants";
import * as errors from "./errors";
import { bigIntToBuffer } from "./tokenOperations/codec";
const createKeccakHash = require("keccak");

Check warning on line 6 in src/address.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Require statement not part of import statement

Expand Down Expand Up @@ -32,15 +32,15 @@ export class Address {
/**
* Creates an address object, given a raw string (whether a hex pubkey or a Bech32 address), a sequence of bytes, or another Address object.
*/
public constructor(value: Address | Buffer | string) {
public constructor(value: Address | Buffer | Uint8Array | string) {
if (!value) {
return;
}
if (value instanceof Address) {
return Address.fromAddress(value);
}
if (value instanceof Buffer) {
return Address.fromBuffer(value);
if (ArrayBuffer.isView(value)) {
return Address.fromBuffer(Buffer.from(value));
}
if (typeof value === "string") {
return Address.fromString(value);
Expand Down
2 changes: 1 addition & 1 deletion src/converters/transactionsConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class TransactionsConverter {
// Before Sirius, there was no "additionalData" field on transaction logs.
// After Sirius, the "additionalData" field includes the payload of the legacy "data" field, as well (as its first element):
// https://github.com/multiversx/mx-chain-go/blob/v1.6.18/process/transactionLog/process.go#L159
const legacyData = eventOnNetwork.dataPayload?.valueOf() || Buffer.from([]);
const legacyData = eventOnNetwork.dataPayload?.valueOf() || Buffer.from(eventOnNetwork.data || "");
const dataItems = eventOnNetwork.additionalData?.map((data) => Buffer.from(data.valueOf())) || [];

if (dataItems.length === 0) {
Expand Down
10 changes: 1 addition & 9 deletions src/transactionsOutcomeParsers/resources.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ErrParseTransactionOutcome } from "../errors";

export class TransactionEvent {
address: string;
identifier: string;
Expand Down Expand Up @@ -82,13 +80,7 @@ export function findEventsByPredicate(
}

export function findEventsByIdentifier(transactionOutcome: TransactionOutcome, identifier: string): TransactionEvent[] {
const events = findEventsByPredicate(transactionOutcome, (event) => event.identifier == identifier);

if (events.length == 0) {
throw new ErrParseTransactionOutcome(`cannot find event of type ${identifier}`);
}

return events;
return findEventsByPredicate(transactionOutcome, (event) => event.identifier == identifier);
}

export function gatherAllEvents(transactionOutcome: TransactionOutcome): TransactionEvent[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,129 @@
import { ContractResultItem, ContractResults, TransactionOnNetwork } from "@multiversx/sdk-network-providers";
import {
ContractResultItem,
ContractResults,
TransactionEventTopic,
TransactionOnNetwork,
TransactionEvent as TransactionOnNetworkEvent,
TransactionLogs as TransactionOnNetworkLogs,
} from "@multiversx/sdk-network-providers";
import BigNumber from "bignumber.js";
import { assert } from "chai";
import { Address } from "../address";
import { TransactionsConverter } from "../converters/transactionsConverter";
import { loadAbiRegistry } from "../testutils";
import { SmartContractCallOutcome, TransactionOutcome } from "./resources";
import { SmartContractCallOutcome, TransactionEvent, TransactionLogs, TransactionOutcome } from "./resources";
import { SmartContractTransactionsOutcomeParser } from "./smartContractTransactionsOutcomeParser";

describe("test smart contract transactions outcome parser", () => {
it("parses deploy outcome (minimalistic)", async function () {
const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqqacl85rd0gl2q8wggl8pwcyzcr4fflc5d8ssve45cj");
const deployer = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th");
const codeHash = Buffer.from("abba", "hex");

const parser = new SmartContractTransactionsOutcomeParser();

const parsed = parser.parseDeploy({
transactionOutcome: new TransactionOutcome({
directSmartContractCallOutcome: new SmartContractCallOutcome({
returnCode: "ok",
returnMessage: "ok",
}),
logs: new TransactionLogs({
events: [
new TransactionEvent({
identifier: "SCDeploy",
topics: [contract.getPublicKey(), deployer.getPublicKey(), codeHash],
}),
],
}),
}),
});

assert.equal(parsed.returnCode, "ok");
assert.equal(parsed.returnMessage, "ok");
assert.deepEqual(parsed.contracts, [
{
address: contract.toBech32(),
ownerAddress: deployer.toBech32(),
codeHash: codeHash,
},
]);
});

it("parses deploy outcome", async function () {
const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqqacl85rd0gl2q8wggl8pwcyzcr4fflc5d8ssve45cj");
const deployer = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th");
const codeHash = Buffer.from("abba", "hex");

const parser = new SmartContractTransactionsOutcomeParser();
const transactionsConverter = new TransactionsConverter();

const transactionOnNetwork = new TransactionOnNetwork({
nonce: 7,
logs: new TransactionOnNetworkLogs({
events: [
new TransactionOnNetworkEvent({
identifier: "SCDeploy",
topics: [
new TransactionEventTopic(contract.getPublicKey().toString("base64")),
new TransactionEventTopic(deployer.getPublicKey().toString("base64")),
new TransactionEventTopic(codeHash.toString("base64")),
],
}),
],
}),
contractResults: new ContractResults([
new ContractResultItem({
nonce: 8,
data: "@6f6b",
}),
]),
});

const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork);
const parsed = parser.parseDeploy({ transactionOutcome });

assert.equal(parsed.returnCode, "ok");
assert.equal(parsed.returnMessage, "ok");
assert.deepEqual(parsed.contracts, [
{
address: contract.toBech32(),
ownerAddress: deployer.toBech32(),
codeHash: codeHash,
},
]);
});

it("parses deploy outcome (with error)", async function () {
const deployer = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th");

const parser = new SmartContractTransactionsOutcomeParser();
const transactionsConverter = new TransactionsConverter();

const transactionOnNetwork = new TransactionOnNetwork({
nonce: 7,
logs: new TransactionOnNetworkLogs({
events: [
new TransactionOnNetworkEvent({
identifier: "signalError",
topics: [
new TransactionEventTopic(deployer.getPublicKey().toString("base64")),
new TransactionEventTopic(Buffer.from("wrong number of arguments").toString("base64")),
],
data: "@75736572206572726f72",
}),
],
}),
});

const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork);
const parsed = parser.parseDeploy({ transactionOutcome });

assert.equal(parsed.returnCode, "user error");
assert.equal(parsed.returnMessage, "wrong number of arguments");
assert.deepEqual(parsed.contracts, []);
});

it("parses execute outcome, without ABI (minimalistic)", function () {
const parser = new SmartContractTransactionsOutcomeParser();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Address } from "../address";
import { Err } from "../errors";
import { EndpointDefinition, ResultsParser, ReturnCode, Type, UntypedOutcomeBundle } from "../smartcontracts";
import { TransactionOutcome } from "./resources";
import { TransactionEvent, TransactionOutcome, findEventsByIdentifier } from "./resources";

interface IAbi {
getEndpoint(name: string): EndpointDefinition;
Expand Down Expand Up @@ -35,6 +36,46 @@ export class SmartContractTransactionsOutcomeParser {
this.legacyResultsParser = options?.legacyResultsParser || new ResultsParser();
}

parseDeploy(options: { transactionOutcome: TransactionOutcome }): {
returnCode: string;
returnMessage: string;
contracts: {
address: string;
ownerAddress: string;
codeHash: Uint8Array;
}[];
} {
const directCallOutcome = options.transactionOutcome.directSmartContractCallOutcome;
const events = findEventsByIdentifier(options.transactionOutcome, "SCDeploy");
const contracts = events.map((event) => this.parseScDeployEvent(event));

return {
returnCode: directCallOutcome.returnCode,
returnMessage: directCallOutcome.returnMessage,
contracts: contracts,
};
}

private parseScDeployEvent(event: TransactionEvent): {
address: string;
ownerAddress: string;
codeHash: Uint8Array;
} {
const topicForAddress = event.topics[0];
const topicForOwnerAddress = event.topics[1];
const topicForCodeHash = event.topics[2];

const address = topicForAddress?.length ? new Address(topicForAddress).toBech32() : "";
const ownerAddress = topicForOwnerAddress?.length ? new Address(topicForOwnerAddress).toBech32() : "";
const codeHash = topicForCodeHash;

return {
address,
ownerAddress,
codeHash,
};
}

parseExecute(options: { transactionOutcome: TransactionOutcome; function?: string }): {
values: any[];
returnCode: string;
Expand Down

0 comments on commit 900a8b8

Please sign in to comment.