diff --git a/package-lock.json b/package-lock.json index bf54b9f9..920874bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.0.0-beta.16", + "version": "13.0.0-beta.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.0.0-beta.16", + "version": "13.0.0-beta.17", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 6a1534a4..c2f537e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.0.0-beta.16", + "version": "13.0.0-beta.17", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", @@ -10,10 +10,10 @@ ], "scripts": { "test": "npm run tests-unit", - "tests-unit": "mocha $(find . -name '*.spec.ts' ! -name '*.net.spec.*')", + "tests-unit": "mocha $(find . -name '*.spec.ts' ! -name '*.local.net.spec.*' ! -name '*.devnet.spec.*' ! -name '*.testnet.spec.*')", "tests-localnet": "mocha $(find . -name '*.local.net.spec.ts')", - "tests-devnet": "mocha $(find . -name '*.dev.net.spec.ts')", - "tests-testnet": "mocha $(find . -name '*.test.net.spec.ts')", + "tests-devnet": "mocha $(find . -name '*.devnet.spec.ts')", + "tests-testnet": "mocha $(find . -name '*.testnet.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", diff --git a/src/smartcontracts/typesystem/abiRegistry.spec.ts b/src/smartcontracts/typesystem/abiRegistry.spec.ts index e9930b4e..d3d966e8 100644 --- a/src/smartcontracts/typesystem/abiRegistry.spec.ts +++ b/src/smartcontracts/typesystem/abiRegistry.spec.ts @@ -63,20 +63,20 @@ describe("test abi registry", () => { it("binary codec correctly decodes perform action result", async () => { let bc = new BinaryCodec(); let buff = Buffer.from( - "0588c738a5d26c0e3a2b4f9e8110b540ee9c0b71a3be057569a5a7b0fcb482c8f70000000806f05b59d3b200000000000b68656c6c6f20776f726c6400000000", + "0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107", "hex", ); - let registry = await loadAbiRegistry("src/testdata/multisig.abi.json"); + let registry = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); let performAction = registry.getEndpoint("getActionData"); assert.equal(performAction.output[0].type.getName(), "Action"); let result = bc.decodeTopLevel(buff, performAction.output[0].type); assert.deepEqual( JSON.stringify(result.valueOf()), - `{"name":"SendTransferExecute","fields":[{"to":{"bech32":"erd13rrn3fwjds8r5260n6q3pd2qa6wqkudrhczh26d957c0edyzermshds0k8","pubkey":"88c738a5d26c0e3a2b4f9e8110b540ee9c0b71a3be057569a5a7b0fcb482c8f7"},"egld_amount":"500000000000000000","endpoint_name":{"type":"Buffer","data":[104,101,108,108,111,32,119,111,114,108,100]},"arguments":[]}]}`, + `{"name":"SendTransferExecuteEgld","fields":[{"to":{"bech32":"erd1qqqqqqqqqqqqqpgq6qr0w0zzyysklfneh32eqp2cf383zc89d8sstnkl60","pubkey":"00000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1"},"egld_amount":"42","opt_gas_limit":null,"endpoint_name":{"type":"Buffer","data":[97,100,100]},"arguments":[{"type":"Buffer","data":[7]}]}]}`, ); - assert.equal(result.valueOf().name, "SendTransferExecute"); + assert.equal(result.valueOf().name, "SendTransferExecuteEgld"); }); it("should load ABI containing arrayN and nested structs", async () => { diff --git a/src/testdata/multisig.abi.json b/src/testdata/multisig-full.abi.json similarity index 57% rename from src/testdata/multisig.abi.json rename to src/testdata/multisig-full.abi.json index 01a363c5..401de7cc 100644 --- a/src/testdata/multisig.abi.json +++ b/src/testdata/multisig-full.abi.json @@ -1,19 +1,20 @@ { "buildInfo": { "rustc": { - "version": "1.59.0-nightly", - "commitHash": "399ba6bb377ce02224b57c4d6e127e160fa76b34", - "commitDate": "2022-01-03", + "version": "1.71.0-nightly", + "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", + "commitDate": "2023-05-25", "channel": "Nightly", - "short": "rustc 1.59.0-nightly (399ba6bb3 2022-01-03)" + "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" }, "contractCrate": { "name": "multisig", - "version": "1.0.0" + "version": "1.0.0", + "gitVersion": "v0.45.2.1-reproducible-169-g37d970c" }, "framework": { - "name": "elrond-wasm", - "version": "0.25.0" + "name": "multiversx-sc", + "version": "0.47.2" } }, "docs": [ @@ -38,127 +39,30 @@ }, "endpoints": [ { - "docs": [ - "Allows the contract to receive funds even if it is marked as unpayable in the protocol." - ], - "name": "deposit", + "name": "upgrade", "mutability": "mutable", - "payableInTokens": [ - "*" - ], "inputs": [], "outputs": [] }, { "docs": [ - "Iterates through all actions and retrieves those that are still pending.", - "Serialized full action data:", - "- the action id", - "- the serialized action data", - "- (number of signers followed by) list of signer addresses." - ], - "name": "getPendingActionFullInfo", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic", - "multi_result": true - } - ] - }, - { - "docs": [ - "Returns `true` (`1`) if the user has signed the action.", - "Does not check whether or not the user is still a board member and the signature valid." - ], - "name": "signed", - "mutability": "readonly", - "inputs": [ - { - "name": "user", - "type": "Address" - }, - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "bool" - } - ] - }, - { - "docs": [ - "Indicates user rights.", - "`0` = no rights,", - "`1` = can propose, but not sign,", - "`2` = can propose and sign." - ], - "name": "userRole", - "mutability": "readonly", - "inputs": [ - { - "name": "user", - "type": "Address" - } - ], - "outputs": [ - { - "type": "UserRole" - } - ] - }, - { - "docs": [ - "Lists all users that can sign actions." - ], - "name": "getAllBoardMembers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic
", - "multi_result": true - } - ] - }, - { - "docs": [ - "Lists all proposers that are not board members." - ], - "name": "getAllProposers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic
", - "multi_result": true - } - ] - }, - { - "docs": [ - "Used by board members to sign actions." + "Allows the contract to receive funds even if it is marked as unpayable in the protocol." ], - "name": "sign", + "name": "deposit", "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } + "payableInTokens": [ + "*" ], + "inputs": [], "outputs": [] }, { "docs": [ - "Board members can withdraw their signatures if they no longer desire for the action to be executed.", - "Actions that are left with no valid signatures can be then deleted to free up storage." + "Clears storage pertaining to an action that is no longer supposed to be executed.", + "Any signatures that the action received must first be removed, via `unsign`.", + "Otherwise this endpoint would be prone to abuse." ], - "name": "unsign", + "name": "discardAction", "mutability": "mutable", "inputs": [ { @@ -170,16 +74,15 @@ }, { "docs": [ - "Clears storage pertaining to an action that is no longer supposed to be executed.", - "Any signatures that the action received must first be removed, via `unsign`.", - "Otherwise this endpoint would be prone to abuse." + "Discard all the actions with the given IDs" ], - "name": "discardAction", + "name": "discardBatch", "mutability": "mutable", "inputs": [ { - "name": "action_id", - "type": "u32" + "name": "action_ids", + "type": "variadic", + "multi_arg": true } ], "outputs": [] @@ -212,11 +115,7 @@ ] }, { - "docs": [ - "Denormalized proposer count.", - "It is kept in sync with the user list by the contract." - ], - "name": "getNumProposers", + "name": "getNumGroups", "mutability": "readonly", "inputs": [], "outputs": [ @@ -227,10 +126,10 @@ }, { "docs": [ - "The index of the last proposed action.", - "0 means that no action was ever proposed yet." + "Denormalized proposer count.", + "It is kept in sync with the user list by the contract." ], - "name": "getActionLastIndex", + "name": "getNumProposers", "mutability": "readonly", "inputs": [], "outputs": [ @@ -240,56 +139,25 @@ ] }, { - "docs": [ - "Serialized action data of an action with index." - ], - "name": "getActionData", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "Action" - } - ] - }, - { - "docs": [ - "Gets addresses of all users who signed an action.", - "Does not check if those users are still board members or not,", - "so the result may contain invalid signers." - ], - "name": "getActionSigners", + "name": "getActionGroup", "mutability": "readonly", "inputs": [ { - "name": "action_id", + "name": "group_id", "type": "u32" } ], "outputs": [ { - "type": "List
" + "type": "variadic", + "multi_result": true } ] }, { - "docs": [ - "Gets addresses of all users who signed an action and are still board members.", - "All these signatures are currently valid." - ], - "name": "getActionSignerCount", + "name": "getLastGroupActionId", "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], + "inputs": [], "outputs": [ { "type": "u32" @@ -298,20 +166,12 @@ }, { "docs": [ - "It is possible for board members to lose their role.", - "They are not automatically removed from all actions when doing so,", - "therefore the contract needs to re-check every time when actions are performed.", - "This function is used to validate the signers before performing an action.", - "It also makes it easy to check before performing an action." + "The index of the last proposed action.", + "0 means that no action was ever proposed yet." ], - "name": "getActionValidSignerCount", + "name": "getActionLastIndex", "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], + "inputs": [], "outputs": [ { "type": "u32" @@ -408,12 +268,39 @@ "type": "BigUint" }, { - "name": "opt_function", - "type": "optional", + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", + "type": "variadic", "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeTransferExecuteEsdt", + "mutability": "mutable", + "inputs": [ + { + "name": "to", + "type": "Address" }, { - "name": "arguments", + "name": "tokens", + "type": "List" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", "type": "variadic", "multi_arg": true } @@ -426,7 +313,7 @@ }, { "docs": [ - "Propose a transaction in which the contract will perform a transfer-execute call.", + "Propose a transaction in which the contract will perform an async call call.", "Can call smart contract endpoints directly.", "Can use ESDTTransfer/ESDTNFTTransfer/MultiESDTTransfer to send tokens, while also optionally calling endpoints.", "Works well with builtin functions.", @@ -444,12 +331,11 @@ "type": "BigUint" }, { - "name": "opt_function", - "type": "optional", - "multi_arg": true + "name": "opt_gas_limit", + "type": "Option" }, { - "name": "arguments", + "name": "function_call", "type": "variadic", "multi_arg": true } @@ -521,28 +407,51 @@ ] }, { - "docs": [ - "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." - ], - "name": "quorumReached", - "mutability": "readonly", + "name": "proposeBatch", + "mutability": "mutable", "inputs": [ { - "name": "action_id", - "type": "u32" + "name": "actions", + "type": "variadic", + "multi_arg": true } ], "outputs": [ { - "type": "bool" + "type": "u32" } ] }, { "docs": [ - "Proposers and board members use this to launch signed actions." + "Used by board members to sign actions." ], - "name": "performAction", + "name": "sign", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Sign all the actions in the given batch" + ], + "name": "signBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "signAndPerform", "mutability": "mutable", "inputs": [ { @@ -552,34 +461,597 @@ ], "outputs": [ { - "type": "PerformActionResult" + "type": "optional
", + "multi_result": true } ] - } - ], - "hasCallback": false, - "types": { - "CallActionData": { - "type": "struct", - "fields": [ + }, + { + "name": "signBatchAndPerform", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Board members can withdraw their signatures if they no longer desire for the action to be executed.", + "Actions that are left with no valid signatures can be then deleted to free up storage." + ], + "name": "unsign", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Unsign all actions with the given IDs" + ], + "name": "unsignBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Returns `true` (`1`) if the user has signed the action.", + "Does not check whether or not the user is still a board member and the signature valid." + ], + "name": "signed", + "mutability": "readonly", + "inputs": [ + { + "name": "user", + "type": "Address" + }, + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "unsignForOutdatedBoardMembers", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + }, + { + "name": "outdated_board_members", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "docs": [ + "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." + ], + "name": "quorumReached", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "docs": [ + "Proposers and board members use this to launch signed actions." + ], + "name": "performAction", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "optional
", + "multi_result": true + } + ] + }, + { + "docs": [ + "Perform all the actions in the given batch" + ], + "name": "performBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "dnsRegister", + "onlyOwner": true, + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [ + { + "name": "dns_address", + "type": "Address" + }, + { + "name": "name", + "type": "bytes" + } + ], + "outputs": [] + }, + { + "docs": [ + "Iterates through all actions and retrieves those that are still pending.", + "Serialized full action data:", + "- the action id", + "- the serialized action data", + "- (number of signers followed by) list of signer addresses." + ], + "name": "getPendingActionFullInfo", + "mutability": "readonly", + "inputs": [ + { + "name": "opt_range", + "type": "optional>", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ], + "allow_multiple_var_args": true + }, + { + "docs": [ + "Indicates user rights.", + "`0` = no rights,", + "`1` = can propose, but not sign,", + "`2` = can propose and sign." + ], + "name": "userRole", + "mutability": "readonly", + "inputs": [ + { + "name": "user", + "type": "Address" + } + ], + "outputs": [ + { + "type": "UserRole" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Lists all users that can sign actions." + ], + "name": "getAllBoardMembers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Lists all proposers that are not board members." + ], + "name": "getAllProposers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Serialized action data of an action with index." + ], + "name": "getActionData", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "Action" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Gets addresses of all users who signed an action.", + "Does not check if those users are still board members or not,", + "so the result may contain invalid signers." + ], + "name": "getActionSigners", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "List
" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Gets addresses of all users who signed an action and are still board members.", + "All these signatures are currently valid." + ], + "name": "getActionSignerCount", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "It is possible for board members to lose their role.", + "They are not automatically removed from all actions when doing so,", + "therefore the contract needs to re-check every time when actions are performed.", + "This function is used to validate the signers before performing an action.", + "It also makes it easy to check before performing an action." + ], + "name": "getActionValidSignerCount", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ], + "labels": [ + "multisig-external-view" + ] + } + ], + "events": [ + { + "identifier": "asyncCallSuccess", + "inputs": [ + { + "name": "results", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "asyncCallError", + "inputs": [ + { + "name": "err_code", + "type": "u32", + "indexed": true + }, + { + "name": "err_message", + "type": "bytes", + "indexed": true + } + ] + }, + { + "identifier": "startPerformAction", + "inputs": [ + { + "name": "data", + "type": "ActionFullInfo" + } + ] + }, + { + "identifier": "performChangeUser", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "changed_user", + "type": "Address", + "indexed": true + }, + { + "name": "old_role", + "type": "UserRole", + "indexed": true + }, + { + "name": "new_role", + "type": "UserRole", + "indexed": true + } + ] + }, + { + "identifier": "performChangeQuorum", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "new_quorum", + "type": "u32", + "indexed": true + } + ] + }, + { + "identifier": "performAsyncCall", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, { "name": "to", - "type": "Address" + "type": "Address", + "indexed": true }, { - "name": "egld_amount", - "type": "BigUint" + "name": "egld_value", + "type": "BigUint", + "indexed": true }, { - "name": "endpoint_name", - "type": "bytes" + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true }, { "name": "arguments", - "type": "List" + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performTransferExecuteEgld", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performTransferExecuteEsdt", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "tokens", + "type": "List", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performDeployFromSource", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "source_address", + "type": "Address", + "indexed": true + }, + { + "name": "code_metadata", + "type": "CodeMetadata", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true } ] }, + { + "identifier": "performUpgradeFromSource", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "target_address", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "source_address", + "type": "Address", + "indexed": true + }, + { + "name": "code_metadata", + "type": "CodeMetadata", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + } + ], + "esdtAttributes": [], + "hasCallback": true, + "types": { "Action": { "type": "enum", "variants": [ @@ -628,7 +1100,7 @@ ] }, { - "name": "SendTransferExecute", + "name": "SendTransferExecuteEgld", "discriminant": 5, "fields": [ { @@ -638,8 +1110,18 @@ ] }, { - "name": "SendAsyncCall", + "name": "SendTransferExecuteEsdt", "discriminant": 6, + "fields": [ + { + "name": "0", + "type": "EsdtTransferExecuteData" + } + ] + }, + { + "name": "SendAsyncCall", + "discriminant": 7, "fields": [ { "name": "0", @@ -649,7 +1131,7 @@ }, { "name": "SCDeployFromSource", - "discriminant": 7, + "discriminant": 8, "fields": [ { "name": "amount", @@ -671,7 +1153,7 @@ }, { "name": "SCUpgradeFromSource", - "discriminant": 8, + "discriminant": 9, "fields": [ { "name": "sc_address", @@ -707,6 +1189,10 @@ "name": "action_id", "type": "u32" }, + { + "name": "group_id", + "type": "u32" + }, { "name": "action_data", "type": "Action" @@ -717,32 +1203,83 @@ } ] }, - "PerformActionResult": { + "ActionStatus": { "type": "enum", "variants": [ { - "name": "Nothing", + "name": "Available", "discriminant": 0 }, { - "name": "DeployResult", - "discriminant": 1, - "fields": [ - { - "name": "0", - "type": "Address" - } - ] + "name": "Aborted", + "discriminant": 1 + } + ] + }, + "CallActionData": { + "type": "struct", + "fields": [ + { + "name": "to", + "type": "Address" }, { - "name": "SendAsyncCall", - "discriminant": 2, - "fields": [ - { - "name": "0", - "type": "AsyncCall" - } - ] + "name": "egld_amount", + "type": "BigUint" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "endpoint_name", + "type": "bytes" + }, + { + "name": "arguments", + "type": "List" + } + ] + }, + "EsdtTokenPayment": { + "type": "struct", + "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "token_nonce", + "type": "u64" + }, + { + "name": "amount", + "type": "BigUint" + } + ] + }, + "EsdtTransferExecuteData": { + "type": "struct", + "fields": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "tokens", + "type": "List" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "endpoint_name", + "type": "bytes" + }, + { + "name": "arguments", + "type": "List" } ] }, diff --git a/src/testdata/multisig-full.wasm b/src/testdata/multisig-full.wasm new file mode 100644 index 00000000..acb183e6 Binary files /dev/null and b/src/testdata/multisig-full.wasm differ diff --git a/src/tokenOperations/tokenOperationsFactory.test.net.spec.ts b/src/tokenOperations/tokenOperationsFactory.testnet.spec.ts similarity index 100% rename from src/tokenOperations/tokenOperationsFactory.test.net.spec.ts rename to src/tokenOperations/tokenOperationsFactory.testnet.spec.ts diff --git a/src/transactionsOutcomeParsers/resources.spec.ts b/src/transactionsOutcomeParsers/resources.spec.ts new file mode 100644 index 00000000..1d8fbd8e --- /dev/null +++ b/src/transactionsOutcomeParsers/resources.spec.ts @@ -0,0 +1,69 @@ +import { assert } from "chai"; +import { + SmartContractResult, + TransactionEvent, + TransactionLogs, + TransactionOutcome, + findEventsByFirstTopic, + findEventsByIdentifier, +} from "./resources"; + +describe("test resources", () => { + it("finds events by identifier, by first topic", async function () { + const outcome = new TransactionOutcome({ + logs: new TransactionLogs({ + events: [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("a")], + }), + ], + }), + smartContractResults: [ + new SmartContractResult({ + logs: new TransactionLogs({ + events: [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("b")], + }), + new TransactionEvent({ + identifier: "bar", + topics: [Buffer.from("c")], + }), + ], + }), + }), + ], + }); + + const foundByIdentifierFoo = findEventsByIdentifier(outcome, "foo"); + const foundByIdentifierBar = findEventsByIdentifier(outcome, "bar"); + const foundByTopic = findEventsByFirstTopic(outcome, "b"); + + assert.deepEqual(foundByIdentifierFoo, [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("a")], + }), + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("b")], + }), + ]); + + assert.deepEqual(foundByIdentifierBar, [ + new TransactionEvent({ + identifier: "bar", + topics: [Buffer.from("c")], + }), + ]); + + assert.deepEqual(foundByTopic, [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("b")], + }), + ]); + }); +}); diff --git a/src/transactionsOutcomeParsers/resources.ts b/src/transactionsOutcomeParsers/resources.ts index 93a4d1e5..b30bd2f1 100644 --- a/src/transactionsOutcomeParsers/resources.ts +++ b/src/transactionsOutcomeParsers/resources.ts @@ -83,6 +83,10 @@ export function findEventsByIdentifier(transactionOutcome: TransactionOutcome, i return findEventsByPredicate(transactionOutcome, (event) => event.identifier == identifier); } +export function findEventsByFirstTopic(transactionOutcome: TransactionOutcome, topic: string): TransactionEvent[] { + return findEventsByPredicate(transactionOutcome, (event) => event.topics[0]?.toString() == topic); +} + export function gatherAllEvents(transactionOutcome: TransactionOutcome): TransactionEvent[] { const allEvents = []; diff --git a/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts b/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts index f709cc9b..9ed01c91 100644 --- a/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts +++ b/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts @@ -13,7 +13,7 @@ import { Address } from "../address"; import { TransactionsConverter } from "../converters/transactionsConverter"; import { AbiRegistry } from "../smartcontracts"; import { loadAbiRegistry } from "../testutils"; -import { TransactionEvent, findEventsByIdentifier } from "./resources"; +import { TransactionEvent, findEventsByFirstTopic } from "./resources"; import { TransactionEventsParser } from "./transactionEventsParser"; describe("test transaction events parser", () => { @@ -39,7 +39,7 @@ describe("test transaction events parser", () => { ]); }); - it("parses events", async function () { + it("parses events (esdt-safe, deposit)", async function () { const parser = new TransactionEventsParser({ abi: await loadAbiRegistry("src/testdata/esdt-safe.abi.json"), }); @@ -69,7 +69,7 @@ describe("test transaction events parser", () => { }); const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); - const events = findEventsByIdentifier(transactionOutcome, "deposit"); + const events = findEventsByFirstTopic(transactionOutcome, "deposit"); const parsed = parser.parseEvents({ events }); assert.deepEqual(parsed, [ @@ -92,6 +92,65 @@ describe("test transaction events parser", () => { ]); }); + it("parses events (multisig, startPerformAction)", async function () { + const parser = new TransactionEventsParser({ + abi: await loadAbiRegistry("src/testdata/multisig-full.abi.json"), + }); + + const transactionsConverter = new TransactionsConverter(); + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b", + }), + ]), + logs: new TransactionLogsOnNetwork({ + events: [ + new TransactionEventOnNetwork({ + identifier: "performAction", + topics: [new TransactionEventTopic("c3RhcnRQZXJmb3JtQWN0aW9u")], + dataPayload: new TransactionEventData( + Buffer.from( + "00000001000000000500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000000000000003616464000000010000000107000000010139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "hex", + ), + ), + }), + ], + }), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + const events = findEventsByFirstTopic(transactionOutcome, "startPerformAction"); + const parsed = parser.parseEvents({ events }); + + assert.deepEqual(parsed, [ + { + data: { + action_id: new BigNumber("1"), + group_id: new BigNumber("0"), + action_data: { + name: "SendTransferExecuteEgld", + fields: [ + { + to: Address.fromBech32( + "erd1qqqqqqqqqqqqqpgq6qr0w0zzyysklfneh32eqp2cf383zc89d8sstnkl60", + ), + egld_amount: new BigNumber("0"), + opt_gas_limit: null, + endpoint_name: Buffer.from("add"), + arguments: [Buffer.from("07", "hex")], + }, + ], + }, + signers: [Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")], + }, + }, + ]); + }); + it("cannot parse events, when definition is missing", async function () { const parser = new TransactionEventsParser({ abi: await loadAbiRegistry("src/testdata/esdt-safe.abi.json"), @@ -102,17 +161,18 @@ describe("test transaction events parser", () => { events: [ new TransactionEvent({ identifier: "foobar", + topics: [Buffer.from("doFoobar")], }), ], }); - }, "Invariant failed: [event [foobar] not found]"); + }, "Invariant failed: [event [doFoobar] not found]"); }); it("parses event (with multi-values)", async function () { const abi = AbiRegistry.create({ events: [ { - identifier: "foobar", + identifier: "doFoobar", inputs: [ { name: "a", @@ -139,7 +199,7 @@ describe("test transaction events parser", () => { event: new TransactionEvent({ identifier: "foobar", topics: [ - Buffer.from("foobar"), + Buffer.from("doFoobar"), Buffer.from([42]), Buffer.from("test"), Buffer.from([43]), diff --git a/src/transactionsOutcomeParsers/transactionEventsParser.ts b/src/transactionsOutcomeParsers/transactionEventsParser.ts index 326d65cf..4d568e07 100644 --- a/src/transactionsOutcomeParsers/transactionEventsParser.ts +++ b/src/transactionsOutcomeParsers/transactionEventsParser.ts @@ -17,9 +17,9 @@ export class TransactionEventsParser { // By default, we consider that the first topic is the event identifier. // This is true for log entries emitted by smart contracts: + // https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L270 // https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L283 - this.firstTopicIsIdentifier = - options.firstTopicIsIdentifier === undefined ? true : options.firstTopicIsIdentifier; + this.firstTopicIsIdentifier = options.firstTopicIsIdentifier ?? true; } parseEvents(options: { events: TransactionEvent[] }): any[] { @@ -35,14 +35,15 @@ export class TransactionEventsParser { parseEvent(options: { event: TransactionEvent }): any { const topics = options.event.topics.map((topic) => Buffer.from(topic)); - const dataItems = options.event.dataItems.map((dataItem) => Buffer.from(dataItem)); - const eventDefinition = this.abi.getEvent(options.event.identifier); + const abiIdentifier = this.firstTopicIsIdentifier ? topics[0]?.toString() : options.event.identifier; if (this.firstTopicIsIdentifier) { - // Discard the first topic (not useful). topics.shift(); } + const dataItems = options.event.dataItems.map((dataItem) => Buffer.from(dataItem)); + const eventDefinition = this.abi.getEvent(abiIdentifier); + const parsedEvent = this.legacyResultsParser.doParseEvent({ topics: topics, dataItems: dataItems,