From 14bf65e02b56d26ccc8e4952b30b620b4b3a4682 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 12:30:12 +0300 Subject: [PATCH 01/14] Add multisig controller --- src/multisig/multisigController.spec.ts | 570 ++++++++++++ src/multisig/multisigController.ts | 808 ++++++++++++++++++ src/multisig/resources.ts | 148 +++- src/smartContracts/smartContractController.ts | 4 +- 4 files changed, 1527 insertions(+), 3 deletions(-) create mode 100644 src/multisig/multisigController.spec.ts create mode 100644 src/multisig/multisigController.ts diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts new file mode 100644 index 00000000..45bace69 --- /dev/null +++ b/src/multisig/multisigController.spec.ts @@ -0,0 +1,570 @@ +import { assert } from "chai"; +import { Address, CodeMetadata, SmartContractQueryResponse } from "../core"; +import { loadAbiRegistry, MockNetworkProvider } from "../testutils"; +import { MultisigController } from "./multisigController"; +import * as resources from "./resources"; + +describe("test multisig controller query methods", () => { + const mockMultisigAddress: string = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6"; + const mockBoardMemberAddress = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; + const mockProposerAddress = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"; + let networkProvider = new MockNetworkProvider(); + let controller = new MultisigController({ + chainID: "D", + networkProvider: networkProvider, + }); + + beforeEach(async function () { + networkProvider = new MockNetworkProvider(); + controller = new MultisigController({ + chainID: "D", + networkProvider: networkProvider, + abi: await loadAbiRegistry("src/testdata/multisig-full.abi.json"), + }); + }); + + it("getQuorum returns the quorum value", async function () { + networkProvider.mockQueryContractOnFunction( + "getQuorum", + new SmartContractQueryResponse({ + function: "getQuorum", + returnDataParts: [Buffer.from("03", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getQuorum({ mutisigAddress: mockMultisigAddress }); + + assert.equal(result, 3); + }); + + it("getNumBoardMembers returns the number of board members", async function () { + networkProvider.mockQueryContractOnFunction( + "getNumBoardMembers", + new SmartContractQueryResponse({ + function: "getNumBoardMembers", + returnDataParts: [Buffer.from("02", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + const result = await controller.getNumBoardMembers({ mutisigAddress: mockMultisigAddress }); + + assert.equal(result, 2); + }); + + it("queries and returns the number of groups", async function () { + networkProvider.mockQueryContractOnFunction( + "getNumGroups", + new SmartContractQueryResponse({ + function: "getNumGroups", + returnDataParts: [Buffer.from("05", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getNumGroups({ mutisigAddress: mockMultisigAddress }); + + assert.equal(result, 5); + }); + + it("getNumProposers returns the number of proposers", async function () { + networkProvider.mockQueryContractOnFunction( + "getNumProposers", + new SmartContractQueryResponse({ + function: "getNumProposers", + returnDataParts: [Buffer.from("04", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getNumProposers({ mutisigAddress: mockMultisigAddress }); + + assert.equal(result, 4); + }); + + it("getActionGroup returns the action group ID", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionGroup", + new SmartContractQueryResponse({ + function: "getActionGroup", + returnDataParts: [Buffer.from("02", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionGroup({ + mutisigAddress: mockMultisigAddress, + groupId: 5, + }); + assert.equal(result.length, 1); + assert.equal(result[0], 2); + }); + + it("getLastGroupActionId returns the last group action ID", async function () { + networkProvider.mockQueryContractOnFunction( + "getLastGroupActionId", + new SmartContractQueryResponse({ + function: "getLastGroupActionId", + returnDataParts: [Buffer.from("07", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getLastGroupActionId({ + mutisigAddress: mockMultisigAddress, + }); + + assert.equal(result, 7); + }); + + it("getActionLastIndex returns the last action ID", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionLastIndex", + new SmartContractQueryResponse({ + function: "getActionLastIndex", + returnDataParts: [Buffer.from("42", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionLastIndex({ + mutisigAddress: mockMultisigAddress, + }); + + assert.equal(result, 0x42); + }); + + it("hasSignedAction returns whether user has signed action", async function () { + networkProvider.mockQueryContractOnFunction( + "signed", + new SmartContractQueryResponse({ + function: "signed", + returnDataParts: [Buffer.from("01", "hex")], // 1 = true + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.hasSignedAction({ + mutisigAddress: mockMultisigAddress, + userAddress: mockBoardMemberAddress, + actionId: 42, + }); + + assert.isTrue(result); + + it("returns false when user has not signed", async function () { + networkProvider.mockQueryContractOnFunction( + "signed", + new SmartContractQueryResponse({ + function: "signed", + returnDataParts: [Buffer.from("00", "hex")], // 0 = false + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.hasSignedAction({ + mutisigAddress: mockMultisigAddress, + userAddress: mockProposerAddress, + actionId: 42, + }); + + assert.isFalse(result); + }); + }); + + it("quorumReached returns false when quorum reached", async function () { + networkProvider.mockQueryContractOnFunction( + "quorumReached", + new SmartContractQueryResponse({ + function: "quorumReached", + returnDataParts: [Buffer.from("01", "hex")], // 1 = true + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.quorumReached({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + assert.isTrue(result); + + it("quorumReached returns false when quorum not reached", async function () { + networkProvider.mockQueryContractOnFunction( + "quorumReached", + new SmartContractQueryResponse({ + function: "quorumReached", + returnDataParts: [Buffer.from("00", "hex")], // 0 = false + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.quorumReached({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + assert.isFalse(result); + }); + }); + + it("getUserRole returns the user role", async function () { + networkProvider.mockQueryContractOnFunction( + "userRole", + new SmartContractQueryResponse({ + function: "userRole", + returnDataParts: [Buffer.from("01", "hex")], // 1 = BOARD_MEMBER, for example + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getUserRole({ + mutisigAddress: mockMultisigAddress, + userAddress: mockBoardMemberAddress, + }); + + assert.equal(result, "Proposer"); // 1 could be board member role + }); + + it("getAllBoardMembers returns all board members as address array", async function () { + // Prepare addresses for the mock response + const address1 = Buffer.from(Address.newFromBech32(mockBoardMemberAddress).toHex(), "hex"); + const address2 = Buffer.from(Address.newFromBech32(mockProposerAddress).toHex(), "hex"); + networkProvider.mockQueryContractOnFunction( + "getAllBoardMembers", + new SmartContractQueryResponse({ + function: "getAllBoardMembers", + returnDataParts: [address1, address2], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getAllBoardMembers({ + mutisigAddress: mockMultisigAddress, + }); + + assert.equal(result.length, 2); + assert.equal(result[0], mockBoardMemberAddress); + assert.equal(result[1], mockProposerAddress); + }); + + it("getAllProposers returns all proposers as address array", async function () { + const address1 = Buffer.from(Address.newFromBech32(mockBoardMemberAddress).toHex(), "hex"); + const address2 = Buffer.from(Address.newFromBech32(mockProposerAddress).toHex(), "hex"); + + networkProvider.mockQueryContractOnFunction( + "getAllProposers", + new SmartContractQueryResponse({ + function: "getAllProposers", + returnDataParts: [address1, address2], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getAllProposers({ + mutisigAddress: mockMultisigAddress, + }); + + assert.equal(result.length, 2); + assert.equal(result[0], mockBoardMemberAddress); + assert.equal(result[1], mockProposerAddress); + }); + + it("getActionData returns the action data as SendTransferExecuteEgld", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [ + Buffer.from( + "0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107", + "hex", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + const mappedRes = result as resources.SendTransferExecuteEgld; + assert.equal(mappedRes.receiver.toBech32(), "erd1qqqqqqqqqqqqqpgq6qr0w0zzyysklfneh32eqp2cf383zc89d8sstnkl60"); + assert.equal(mappedRes.funcionName, "add"); + assert.equal(mappedRes.amount, 42n); + }); + + // TODO: I'll do this on a future branch + it.skip("getActionData returns the action data as SendAsyncCall", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [ + Buffer.from( + "BwAAAAAAAAAABQB40pYyrLFZmAA/YV0KUSYTU9gEHT4TAAAACA3gtrOnZAAAAQAAAAADk4cAAAAAAQoAAAACAAAAAQ0AAAABDQ==", + "base64", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + const mappedRes = result as resources.SendAsyncCall; + assert.equal(mappedRes.receiver.toBech32(), "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms"); + assert.equal(mappedRes.funcionName, "add"); + assert.equal(mappedRes.amount, 0n); + }); + + it("getActionData returns the action data as SendTransferExecuteEsdt", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [ + Buffer.from( + "BgAAAAAAAAAABQBJv/ljvfo+oCcTNiCV3zLj1wjqzPxXAAAAAQAAAAxBTElDRS01NjI3ZjEAAAAAAAAAAAAAAAEKAQAAAAAATEtAAAAAFDY0Njk3Mzc0NzI2OTYyNzU3NDY1AAAAAA==", + "base64", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + const mappedRes = result as resources.SendTransferExecuteEsdt; + + assert.equal(mappedRes.receiver.toBech32(), "erd1qqqqqqqqqqqqqpgqfxlljcaalgl2qfcnxcsftheju0ts36kvl3ts3qkewe"); + assert.equal(mappedRes.funcionName, "distribute"); + }); + + it("getActionData returns the action data as AddBoardMember", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [Buffer.from("AYBJ1jnlppgNHNI5KrzOQQKc2nShVjUjogLwlkHMJhj4", "base64")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + const mappedRes = result as resources.AddBoardMember; + + assert.equal(mappedRes.address.toBech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + }); + + it("getActionData returns the action data as AddProposer", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [Buffer.from("AYBJ1jnlppgNHNI5KrzOQQKc2nShVjUjogLwlkHMJhj4", "base64")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + const mappedRes = result as resources.AddProposer; + + assert.equal(mappedRes.address.toBech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + }); + + it("getActionData returns the action data as SCDeployFromSource", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [ + Buffer.from( + "CAAAAAexorwuxQAAAAAAAAAAAAAFAIcNBBLO3ocYU6HC1Ip1Q8Bz6zn5aeEFAAAAAAEAAAABBw==", + "base64", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + const mappedRes = result as resources.SCDeployFromSource; + + assert.equal( + mappedRes.sourceContractAddress.toBech32(), + "erd1qqqqqqqqqqqqqpgqsuxsgykwm6r3s5apct2g5a2rcpe7kw0ed8ssf6h9f6", + ); + assert.equal(mappedRes.amount.toString(), "50000000000000000"); + assert.deepEqual(mappedRes.codeMetadata, new CodeMetadata(true, true, false)); + }); + + it("getActionData returns the action data as SCUpgradeFromSource", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [ + Buffer.from( + "CQAAAAAAAAAABQB+Jc5t66x0jYa105MSCrHrAqRtWBZ5AAAAB7GivC7FAAAAAAAAAAAAAAUAar0cOjeU2gFgK4VawD54IeZjjsgWeQUAAAAAAA==", + "base64", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + const amount = BigInt(50000000000000000); // 0.05 EGLD + const metadata = new CodeMetadata(true, true, false); + const sourceContract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqd273cw3hjndqzcpts4dvq0ncy8nx8rkgzeusnefvaq"); + + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + const mappedRes = result as resources.SCUpgradeFromSource; + + assert.equal(mappedRes.sourceContractAddress.toBech32(), sourceContract.toBech32()); + assert.equal(mappedRes.amount, amount); + assert.deepEqual(mappedRes.codeMetadata, metadata); + }); + + it("getActionData returns the action data as ChangeQuorum", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [Buffer.from("BAAAAAI=", "base64")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + const mappedRes = result as resources.ChangeQuorum; + + assert.equal(mappedRes.quorum, 2); + }); + + it("getActionData returns the action data as RemoveUser", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionData", + new SmartContractQueryResponse({ + function: "getActionData", + returnDataParts: [Buffer.from("A4BJ1jnlppgNHNI5KrzOQQKc2nShVjUjogLwlkHMJhj4", "base64")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + const result = await controller.getActionData({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + const mappedRes = result as resources.RemoveUser; + + assert.equal(mappedRes.address.toBech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + }); + + it("getActionSigners returns the action signers as address array", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionSigners", + new SmartContractQueryResponse({ + function: "getActionSigners", + returnDataParts: [ + Buffer.from( + "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + "hex", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionSigners({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + assert.equal(result.length, 2); + assert.equal(result[0], mockBoardMemberAddress); + assert.equal(result[1], mockProposerAddress); + }); + + it("getActionSignerCount returns the number of signers that signed an action", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionSignerCount", + new SmartContractQueryResponse({ + function: "getActionSignerCount", + returnDataParts: [Buffer.from("04", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionSignerCount({ mutisigAddress: mockMultisigAddress, actionId: 42 }); + + assert.equal(result, 4); + }); + + it("getActionValidSignerCount returns the number of signers that signed an action and are still boardMembers", async function () { + networkProvider.mockQueryContractOnFunction( + "getActionValidSignerCount", + new SmartContractQueryResponse({ + function: "getActionValidSignerCount", + returnDataParts: [Buffer.from("04", "hex")], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getActionValidSignerCount({ + mutisigAddress: mockMultisigAddress, + actionId: 42, + }); + + assert.equal(result, 4); + }); +}); diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts new file mode 100644 index 00000000..79c9855b --- /dev/null +++ b/src/multisig/multisigController.ts @@ -0,0 +1,808 @@ +import BigNumber from "bignumber.js"; +import { Abi } from "../abi"; +import { + Address, + BaseControllerInput, + IAccount, + Transaction, + TransactionsFactoryConfig, + TransactionWatcher, +} from "../core"; +import { INetworkProvider } from "../networkProviders/interface"; +import { SmartContractController } from "../smartContracts"; +import { MultisigTransactionsFactory } from "./multisigTransactionsFactory"; +import { MultisigTransactionsOutcomeParser } from "./multisigTransactionsOutcomeParser"; +import * as resources from "./resources"; +export class MultisigController extends SmartContractController { + private transactionAwaiter: TransactionWatcher; + private multisigFactory: MultisigTransactionsFactory; + private multisigParser: MultisigTransactionsOutcomeParser; + + constructor(options: { chainID: string; networkProvider: INetworkProvider; abi?: Abi }) { + super(options); + this.abi = options.abi; + this.transactionAwaiter = new TransactionWatcher(options.networkProvider); + this.multisigFactory = new MultisigTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: options.chainID }), + abi: options.abi, + }); + this.multisigParser = new MultisigTransactionsOutcomeParser(); + } + + /** + * Creates a transaction for deploying a new multisig contract + */ + async createTransactionForMultisigDeploy( + sender: IAccount, + nonce: bigint, + options: resources.DeployMultisigContractInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForMultisigDeploy(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Gets quorum for specific multisig + */ + async getQuorum(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getQuorum", + arguments: [], + }); + return Number(response[0].toString()); + } + + /** + * Gets number of board members for specific multisig + */ + async getNumBoardMembers(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getNumBoardMembers", + arguments: [], + }); + + return Number(response[0].toString()); + } + + /** + * Gets number of groups for specific multisig + */ + async getNumGroups(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getNumGroups", + arguments: [], + }); + + return Number(Buffer.from(response[0]).toString()); + } + + /** + * Gets number of proposers for specific multisig + */ + async getNumProposers(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getNumProposers", + arguments: [], + }); + + return Number(Buffer.from(response[0]).toString()); + } + + /** + * Gets action group for specific multisig + */ + async getActionGroup(options: { mutisigAddress: string; groupId: number }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getActionGroup", + arguments: [options.groupId], + }); + + return response[0].map((n: BigNumber) => Number(n.toString())); + } + + /** + * Gets last group action id specific multisig + */ + async getLastGroupActionId(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getLastGroupActionId", + arguments: [], + }); + + return Number(response[0].toString()); + } + + /** + * Gets last action index specific multisig + */ + async getActionLastIndex(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getActionLastIndex", + arguments: [], + }); + + return Number(response[0].toString()); + } + + /** + * 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. + */ + async hasSignedAction(options: { + mutisigAddress: string; + userAddress: string; + actionId: number; + }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "signed", + arguments: [Address.newFromBech32(options.userAddress), options.actionId], + }); + + return response[0]; + } + + /** + * Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`. + */ + async quorumReached(options: { mutisigAddress: string; actionId: number }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "quorumReached", + arguments: [options.actionId], + }); + + return response[0]; + } + + /** + * Lists all users that can sign actions. + */ + async getAllBoardMembers(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getAllBoardMembers", + arguments: [], + }); + + return response[0].map((address: Address) => address.toBech32()); + } + + /** + * Lists all proposers that are not board members. + */ + async getAllProposers(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getAllProposers", + arguments: [], + }); + + return response[0].map((address: Address) => new Address(address).toBech32()); + } + /** + * "Indicates user rights.", + * `0` = no rights,", + * `1` = can propose, but not sign, + * `2` = can propose and sign. + */ + async getUserRole(options: { mutisigAddress: string; userAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "userRole", + arguments: [Address.newFromBech32(options.userAddress)], + }); + const userRole = response[0].valueOf().name as keyof typeof resources.UserRoleEnum; + return resources.UserRoleEnum[userRole]; + } + + /** + * Serialized action data of an action with index. + */ + async getActionData(options: { mutisigAddress: string; actionId: number }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getActionData", + arguments: [options.actionId], + }); + const result = this.mapResponseToAction(response[0].valueOf()); + return result; + } + + /** + * 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. + */ + async getActionSigners(options: { mutisigAddress: string; actionId: number }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getActionSigners", + arguments: [options.actionId], + }); + const addresses: any = response.valueOf(); + return addresses[0]; + } + + /** + * Gets addresses of all users who signed an action and are still board members. + * All these signatures are currently valid. + */ + async getActionSignerCount(options: { mutisigAddress: string; actionId: number }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getActionSignerCount", + arguments: [options.actionId], + }); + + return response[0]; + } + + /** + * Gets addresses of all users who signed an action and are still board members. + * All these signatures are currently valid. + */ + async getActionValidSignerCount(options: { mutisigAddress: string; actionId: number }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getActionValidSignerCount", + arguments: [options.actionId], + }); + + return Number(Buffer.from(response[0]).toString()); + } + + /** + * Creates a transaction for proposing to add a board member + */ + async createTransactionForProposeAddBoardMember( + sender: IAccount, + nonce: bigint, + options: resources.ProposeAddBoardMemberInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeAddBoardMember(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose add board member action + */ + async awaitCompletedProposeAddBoardMember(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to add a proposer + */ + async createTransactionForProposeAddProposer( + sender: IAccount, + nonce: bigint, + options: resources.ProposeAddProposerInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeAddProposer(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose add proposer action + */ + async awaitCompletedProposeAddProposer(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to remove a user + */ + async createTransactionForProposeRemoveUser( + sender: IAccount, + nonce: bigint, + options: resources.ProposeRemoveUserInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeRemoveUser(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose remove user action + */ + async awaitCompletedProposeRemoveUser(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to change quorum + */ + async createTransactionForProposeChangeQuorum( + sender: IAccount, + nonce: bigint, + options: resources.ProposeChangeQuorumInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeChangeQuorum(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose change quorum action + */ + async awaitCompletedProposeChangeQuorum(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for signing an action + */ + async createTransactionForSignAction( + sender: IAccount, + nonce: bigint, + options: resources.ActionInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForSignAction(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a sign action + */ + async awaitCompletedSignAction(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for performing an action + */ + async createTransactionForPerformAction( + sender: IAccount, + nonce: bigint, + options: resources.ActionInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForPerformAction(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a perform action + */ + async awaitCompletedPerformAction(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for unsigning an action + */ + async createTransactionForUnsignAction( + sender: IAccount, + nonce: bigint, + options: resources.ActionInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForUnsign(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of an unsign action + */ + async awaitCompletedUnsignAction(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for discarding an action + */ + async createTransactionForDiscardAction( + sender: IAccount, + nonce: bigint, + options: resources.ActionInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForDiscardAction(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a discard action + */ + async awaitCompletedDiscardAction(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for deposit native token or tokens + */ + async createTransactionForDeposit( + sender: IAccount, + nonce: bigint, + options: resources.DepositExecuteInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForDeposit(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose transfer execute action + */ + async awaitCompletedDepositExecute(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to transfer EGLD and execute a smart contract call + */ + async createTransactionForProposeTransferExecute( + sender: IAccount, + nonce: bigint, + options: resources.ProposeTransferExecuteInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeTransferExecute(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose transfer execute action + */ + async awaitCompletedProposeTransferExecute(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to transfer ESDT tokens and execute a smart contract call + */ + async createTransactionForProposeTransferExecuteEsdt( + sender: IAccount, + nonce: bigint, + options: resources.ProposeTransferExecuteEsdtInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeTransferExecuteEsdt( + sender.address, + options, + ); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose transfer execute ESDT action + */ + async awaitCompletedProposeTransferExecuteEsdt(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing an async call to another contract + */ + async createTransactionForProposeAsyncCall( + sender: IAccount, + nonce: bigint, + options: resources.ProposeAsyncCallInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeAsyncCall(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose async call action + */ + async awaitCompletedProposeAsyncCall(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to deploy a smart contract from source + */ + async createTransactionForProposeSCDeployFromSource( + sender: IAccount, + nonce: bigint, + options: resources.ProposeSCDeployFromSourceInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeSCDeployFromSource(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose SC deploy from source action + */ + async awaitCompletedProposeSCDeployFromSource(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for proposing to upgrade a smart contract from source + */ + async createTransactionForProposeSCUpgradeFromSource( + sender: IAccount, + nonce: bigint, + options: resources.ProposeSCUpgradeFromSourceInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForProposeSCUpgradeFromSource( + sender.address, + options, + ); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a propose SC upgrade from source action + */ + async awaitCompletedProposeSCUpgradeFromSource(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); + } + + /** + * Creates a transaction for signing a batch of actions + */ + async createTransactionForSignBatch( + sender: IAccount, + nonce: bigint, + options: resources.GroupInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForSignBatch(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a sign batch action + */ + async awaitCompletedSignBatch(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for signing and performing an action in one step + */ + async createTransactionForSignAndPerform( + sender: IAccount, + nonce: bigint, + options: resources.ActionInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForSignAndPerform(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a sign and perform action + */ + async awaitCompletedSignAndPerform(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for unsigning for outdated board members + */ + async createTransactionForUnsignForOutdatedBoardMembers( + sender: IAccount, + nonce: bigint, + options: resources.UnsignForOutdatedBoardMembersInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForUnsignForOutdatedBoardMembers( + sender.address, + options, + ); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of an unsign for outdated board members action + */ + async awaitCompletedUnsignForOutdatedBoardMembers(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for performing a batch of actions + */ + async createTransactionForPerformBatch( + sender: IAccount, + nonce: bigint, + options: resources.GroupInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForPerformBatch(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a perform action batch + */ + async awaitCompletedPerformActionBatch(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + /** + * Creates a transaction for discarding a batch of actions + */ + async createTransactionForDiscardBatch( + sender: IAccount, + nonce: bigint, + options: resources.DiscardBatchInput & BaseControllerInput, + ): Promise { + const transaction = this.multisigFactory.createTransactionForDiscardBatch(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + /** + * Awaits the completion of a discard batch action + */ + async awaitCompletedDiscardBatch(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); + } + + private mapResponseToAction = (responseData: any): resources.MultisigAction => { + const { name, fields } = responseData; + switch (name) { + case resources.MultisigActionEnum.Nothing: + return new resources.MultisigAction(); + case resources.MultisigActionEnum.AddBoardMember: + return new resources.AddBoardMember(fields[0]); + case resources.MultisigActionEnum.AddProposer: + return new resources.AddProposer(fields[0]); + case resources.MultisigActionEnum.RemoveUser: + return new resources.RemoveUser(fields[0]); + case resources.MultisigActionEnum.ChangeQuorum: + return new resources.ChangeQuorum(fields[0]); + case resources.MultisigActionEnum.SendTransferExecuteEgld: + return new resources.SendTransferExecuteEgld(fields[0]); + case resources.MultisigActionEnum.SendTransferExecuteEsdt: + return new resources.SendTransferExecuteEsdt(fields[0]); + case resources.MultisigActionEnum.SendAsyncCall: + return new resources.SendAsyncCall(fields[0]); + case resources.MultisigActionEnum.SCDeployFromSource: + return new resources.SCDeployFromSource(fields); + case resources.MultisigActionEnum.SCUpgradeFromSource: + return new resources.SCUpgradeFromSource(fields); + default: + throw new Error(`Unknown action type: ${name}`); + } + }; +} diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 29dac7b5..45a09d59 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -1,5 +1,5 @@ import { Abi, BytesValue } from "../abi"; -import { TokenTransfer, TransactionsFactoryConfig } from "../core"; +import { Token, TokenTransfer, TransactionsFactoryConfig } from "../core"; import { Address } from "../core/address"; import { CodeMetadata } from "../core/codeMetadata"; import { ARGUMENTS_SEPARATOR } from "../core/constants"; @@ -252,6 +252,152 @@ export type DiscardBatchInput = MultisigContractInput & { actionIds: number[]; }; +export enum UserRoleEnum { + None = "None", + Proposer = "Proposer", + BoardMember = "BoardMember", +} + +export enum MultisigActionEnum { + Nothing = "Nothing", + AddBoardMember = "AddBoardMember", + AddProposer = "AddProposer", + RemoveUser = "RemoveUser", + ChangeQuorum = "ChangeQuorum", + SendTransferExecuteEgld = "SendTransferExecuteEgld", + SendTransferExecuteEsdt = "SendTransferExecuteEsdt", + SendAsyncCall = "SendAsyncCall", + SCDeployFromSource = "SCDeployFromSource", + SCUpgradeFromSource = "SCUpgradeFromSource", +} + +export class MultisigAction { + public type: MultisigActionEnum = MultisigActionEnum.Nothing; +} +export class AddBoardMember extends MultisigAction { + public address: Address; + constructor(address: Address) { + super(); + this.type = MultisigActionEnum.AddBoardMember; + this.address = address; + } +} +export class AddProposer extends MultisigAction { + public address: Address; + constructor(address: Address) { + super(); + this.type = MultisigActionEnum.AddProposer; + this.address = address; + } +} +export class RemoveUser extends MultisigAction { + public type: MultisigActionEnum = MultisigActionEnum.RemoveUser; + public address: Address; + constructor(address: Address) { + super(); + this.type = MultisigActionEnum.RemoveUser; + this.address = address; + } +} + +export class ChangeQuorum extends MultisigAction { + public quorum: number; + constructor(quorum: number) { + super(); + this.type = MultisigActionEnum.ChangeQuorum; + this.quorum = quorum; + } +} + +export class SendTransferExecuteEgld extends MultisigAction { + receiver: Address; + amount: bigint; + optionalGasLimit?: number; + funcionName: string; + arguments: Uint8Array[]; + constructor(data: any) { + super(); + this.type = MultisigActionEnum.SendTransferExecuteEgld; + this.receiver = data.to; + this.amount = data.egld_amount; + this.optionalGasLimit = data.opt_gas_limit; + this.funcionName = data.endpoint_name.toString(); + this.arguments = data.arguments; + } +} +export class SendTransferExecuteEsdt extends MultisigAction { + receiver: Address; + tokens: TokenTransfer[]; + optionalGasLimit?: number; + funcionName: string; + arguments: Uint8Array[]; + constructor(data: any) { + super(); + this.type = MultisigActionEnum.SendTransferExecuteEsdt; + this.receiver = data.to; + this.tokens = data.tokens.map( + (token: { token_identifier: any; nonce: any; amount: any }) => + new TokenTransfer({ + token: new Token({ identifier: token.token_identifier, nonce: token.nonce }), + amount: token.amount, + }), + ); + this.optionalGasLimit = data.opt_gas_limit; + + this.funcionName = Buffer.from(data.endpoint_name.toString(), "hex").toString(); + this.arguments = data.arguments; + } +} + +export class SendAsyncCall extends MultisigAction { + receiver: Address; + amount: bigint; + optionalGasLimit?: number; + funcionName: string; + arguments: Uint8Array[]; + constructor(data: any) { + super(); + this.type = MultisigActionEnum.SendAsyncCall; + this.receiver = data.to; + this.amount = data.egld_amount; + this.optionalGasLimit = data.opt_gas_limit; + this.funcionName = data.endpoint_name.toString(); + this.arguments = data.arguments; + } +} + +export class SCDeployFromSource extends MultisigAction { + sourceContractAddress: Address; + amount: bigint; + codeMetadata: CodeMetadata; + arguments: Uint8Array[]; + constructor(data: any) { + super(); + this.type = MultisigActionEnum.SCDeployFromSource; + this.sourceContractAddress = data[1]; + this.amount = data[0]; + this.codeMetadata = data[2]; + this.arguments = data[3]; + } +} + +export class SCUpgradeFromSource extends MultisigAction { + sourceContractAddress: Address; + scAddress: Address; + amount: bigint; + codeMetadata: CodeMetadata; + arguments: Uint8Array[]; + constructor(data: any) { + super(); + this.type = MultisigActionEnum.SCUpgradeFromSource; + this.scAddress = data[0]; + this.amount = data[1]; + this.sourceContractAddress = data[2]; + this.codeMetadata = data[3]; + this.arguments = data[4]; + } +} + export type CallActionData = { receiver: Address; amount: bigint; diff --git a/src/smartContracts/smartContractController.ts b/src/smartContracts/smartContractController.ts index cd370bbc..59efe28f 100644 --- a/src/smartContracts/smartContractController.ts +++ b/src/smartContracts/smartContractController.ts @@ -20,11 +20,11 @@ import * as resources from "./resources"; import { SmartContractTransactionsFactory } from "./smartContractTransactionsFactory"; export class SmartContractController extends BaseController { - private factory: SmartContractTransactionsFactory; + protected factory: SmartContractTransactionsFactory; private parser: SmartContractTransactionsOutcomeParser; private transactionWatcher: TransactionWatcher; private networkProvider: INetworkProvider; - private abi?: Abi; + protected abi?: Abi; constructor(options: { chainID: string; networkProvider: INetworkProvider; abi?: Abi }) { super(); From a215c4f3580341660fab9759a09424d1d3a596bf Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 13:39:21 +0300 Subject: [PATCH 02/14] Add parser --- src/multisig/multisigController.ts | 5 +- .../multisigTransactionsOutcomeParser.ts | 278 ++++++++++++++++++ 2 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 src/multisig/multisigTransactionsOutcomeParser.ts diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 79c9855b..03def853 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -499,9 +499,8 @@ export class MultisigController extends SmartContractController { /** * Awaits the completion of a propose transfer execute action */ - async awaitCompletedDepositExecute(txHash: string): Promise { - const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + async awaitCompletedDepositExecute(txHash: string): Promise { + await this.transactionAwaiter.awaitCompleted(txHash); } /** diff --git a/src/multisig/multisigTransactionsOutcomeParser.ts b/src/multisig/multisigTransactionsOutcomeParser.ts new file mode 100644 index 00000000..28c100d2 --- /dev/null +++ b/src/multisig/multisigTransactionsOutcomeParser.ts @@ -0,0 +1,278 @@ +import { TransactionOnNetwork } from "../core"; +import { Address } from "../core/address"; +import { Err } from "../core/errors"; +import { TransactionEvent } from "../core/transactionEvents"; +import { SmartContractCallOutcome, SmartContractResult } from "../transactionsOutcomeParsers/resources"; + +enum Events { + MultisigDeploy = "MultisigDeploy", + MultisigActionProp = "MultisigActionProposal", + SignalError = "signalError", + WriteLog = "writeLog", +} + +/** + * Parses the outcome of multisig contract operations + */ +export class MultisigTransactionsOutcomeParser { + /** + * Parses the outcome of creating a new multisig contract + * @param transactionOnNetwork The completed transaction + * @returns An array of objects containing the new contract addresses + */ + parseDeployMultisigContract(transactionOnNetwork: TransactionOnNetwork): { contractAddress: Address }[] { + const directCallOutcome = this.findDirectMultisigDeployOutcome(transactionOnNetwork); + + if (!directCallOutcome || directCallOutcome.returnCode !== "ok") { + return []; + } + + // Look for the deployment events + const events = transactionOnNetwork.logs.events + .concat(transactionOnNetwork.smartContractResults.flatMap((result) => result.logs.events)) + .filter((event) => event.identifier === Events.MultisigDeploy); + + return events.map((event) => { + // Assuming the contract address is in the first topic + const addressBytes = Buffer.from(event.topics[0]); + const address = Address.newFromHex(addressBytes.toString("hex")); + return { contractAddress: address }; + }); + } + + /** + * Parses the outcome of a multisig action proposal + * @param transactionOnNetwork The completed transaction + * @returns The action ID that was created + */ + parseActionProposal(transactionOnNetwork: TransactionOnNetwork): number { + const directCallOutcome = this.findDirectMultisigCallOutcome(transactionOnNetwork); + + if (!directCallOutcome || directCallOutcome.returnCode !== "ok") { + throw new Err("Failed to propose action: " + directCallOutcome.returnMessage); + } + + if (directCallOutcome.returnDataParts.length === 0) { + throw new Err("No action ID returned in the transaction outcome"); + } + + // Assuming the first return data part contains the action ID as bytes + const actionIdBytes = directCallOutcome.returnDataParts[0]; + return parseInt(Buffer.from(actionIdBytes).toString("hex"), 16); + } + + /** + * Finds the direct smart contract call outcome from a transaction + */ + protected findDirectMultisigCallOutcome(transactionOnNetwork: TransactionOnNetwork): SmartContractCallOutcome { + let outcome = this.findDirectMultisigCallOutcomeWithinSmartContractResults(transactionOnNetwork); + + if (outcome) { + return outcome; + } + + outcome = this.findDirectMultisigCallOutcomeIfError(transactionOnNetwork); + + if (outcome) { + return outcome; + } + + outcome = this.findDirectMultisigCallOutcomeWithinWriteLogEvents(transactionOnNetwork); + + if (outcome) { + return outcome; + } + + return new SmartContractCallOutcome({ + function: transactionOnNetwork.function, + returnCode: "", + returnMessage: "", + returnDataParts: [], + }); + } + + /** + * Similar to findDirectSmartContractCallOutcome but specifically for multisig deploy operations + */ + protected findDirectMultisigDeployOutcome(transactionOnNetwork: TransactionOnNetwork): SmartContractCallOutcome { + return this.findDirectMultisigCallOutcome(transactionOnNetwork); + } + + /** + * Finds the call outcome within smart contract results + */ + protected findDirectMultisigCallOutcomeWithinSmartContractResults( + transactionOnNetwork: TransactionOnNetwork, + ): SmartContractCallOutcome | null { + // Similar implementation to SmartContractTransactionsOutcomeParser but adapted for multisig + const eligibleResults: SmartContractResult[] = []; + + for (const result of transactionOnNetwork.smartContractResults) { + const matchesCriteria = + result.data.toString().startsWith("@") && + result.receiver.toBech32() === transactionOnNetwork.sender.toBech32(); + + if (matchesCriteria) { + eligibleResults.push(result); + } + } + + if (eligibleResults.length === 0) { + return null; + } + + if (eligibleResults.length > 1) { + throw new Error(`More than one smart contract result found for transaction: ${transactionOnNetwork.hash}`); + } + + const [result] = eligibleResults; + const parts = result.data.toString().split("@").filter(Boolean); + + let returnCode = "ok"; + let returnMessage = "success"; + + if (parts.length > 0) { + returnCode = Buffer.from(parts[0], "hex").toString() || "ok"; + } + + if (result.raw["returnMessage"]) { + returnMessage = result.raw["returnMessage"]; + } + + const returnDataParts = parts.slice(1).map((part) => Buffer.from(part, "hex")); + + return new SmartContractCallOutcome({ + function: transactionOnNetwork.function, + returnCode: returnCode, + returnMessage: returnMessage, + returnDataParts: returnDataParts, + }); + } + + /** + * Finds the call outcome if there was an error + */ + protected findDirectMultisigCallOutcomeIfError( + transactionOnNetwork: TransactionOnNetwork, + ): SmartContractCallOutcome | null { + const eventIdentifier = Events.SignalError; + const eligibleEvents: TransactionEvent[] = []; + + // First, look in "logs": + eligibleEvents.push( + ...transactionOnNetwork.logs.events.filter((event) => event.identifier === eventIdentifier), + ); + + // Then, look in "logs" of "contractResults": + for (const result of transactionOnNetwork.smartContractResults) { + eligibleEvents.push(...result.logs.events.filter((event) => event.identifier === eventIdentifier)); + } + + if (eligibleEvents.length === 0) { + return null; + } + + const [event] = eligibleEvents; + const lastTopic = event.topics[event.topics.length - 1]?.toString(); + + return new SmartContractCallOutcome({ + function: transactionOnNetwork.function, + returnCode: "error", + returnMessage: lastTopic || "Unknown error", + returnDataParts: [], + }); + } + + /** + * Finds the call outcome within write log events + */ + protected findDirectMultisigCallOutcomeWithinWriteLogEvents( + transactionOnNetwork: TransactionOnNetwork, + ): SmartContractCallOutcome | null { + const eventIdentifier = Events.WriteLog; + const eligibleEvents: TransactionEvent[] = []; + + // First, look in "logs": + eligibleEvents.push( + ...transactionOnNetwork.logs.events.filter((event) => event.identifier === eventIdentifier), + ); + + // Then, look in "logs" of "contractResults": + for (const result of transactionOnNetwork.smartContractResults) { + eligibleEvents.push(...result.logs.events.filter((event) => event.identifier === eventIdentifier)); + } + + if (eligibleEvents.length === 0) { + return null; + } + + const [event] = eligibleEvents; + const data = Buffer.from(event.data).toString(); + const parts = data.split("@").filter(Boolean); + + let returnCode = "ok"; + if (parts.length > 0) { + returnCode = Buffer.from(parts[0], "hex").toString() || "ok"; + } + + const returnDataParts = parts.slice(1).map((part) => Buffer.from(part, "hex")); + + return new SmartContractCallOutcome({ + function: transactionOnNetwork.function, + returnCode: returnCode, + returnMessage: returnCode, + returnDataParts: returnDataParts, + }); + } + + /** + * Parses the outcome of a query to get the multisig contract's pending actions + * @param queryResponse The query response + * @returns The list of pending action IDs + */ + parsePendingActionIds(queryResponse: string[]): number[] { + try { + if (!queryResponse || queryResponse.length === 0) { + return []; + } + + // Assuming each element in the response is a base64 encoded action ID + return queryResponse.map((item) => { + const buffer = Buffer.from(item, "base64"); + return parseInt(buffer.toString("hex"), 16); + }); + } catch (error) { + throw new Error(`Error parsing pending action IDs: ${error}`); + } + } + + /** + * Parses the outcome of a query to get the multisig contract's board members + * @param queryResponse The query response + * @returns The list of board member addresses + */ + parseBoardMembers(queryResponse: string[]): Address[] { + if (!queryResponse || queryResponse.length === 0) { + return []; + } + + return queryResponse.map((item) => { + const buffer = Buffer.from(item, "base64"); + return Address.newFromHex(buffer.toString("hex")); + }); + } + + /** + * Parses the outcome of a query to get the multisig contract's quorum + * @param queryResponse The query response + * @returns The quorum value + */ + parseQuorum(queryResponse: string[]): number { + if (!queryResponse || queryResponse.length === 0) { + throw new Err("No return data available"); + } + + const buffer = Buffer.from(queryResponse[0], "base64"); + return parseInt(buffer.toString("hex"), 16); + } +} From 925f5d7992daab5b301f50c78cd45dc5227abcbd Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 15:26:47 +0300 Subject: [PATCH 03/14] Update resources --- src/index.ts | 1 + src/multisig/index.ts | 3 + .../multisigTransactionsFactory.spec.ts | 24 +-- src/multisig/multisigTransactionsFactory.ts | 83 +++++---- .../proposeTransferExecuteContract.ts | 90 +++++++++ src/multisig/resources.ts | 174 ++---------------- 6 files changed, 159 insertions(+), 216 deletions(-) create mode 100644 src/multisig/index.ts create mode 100644 src/multisig/proposeTransferExecuteContract.ts diff --git a/src/index.ts b/src/index.ts index b9e1ef27..cb9f585e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export * from "./accounts"; export * from "./core"; export * from "./delegation"; export * from "./entrypoints"; +export * from "./multisig"; export * from "./networkProviders"; export * from "./smartContracts"; export * from "./tokenManagement"; diff --git a/src/multisig/index.ts b/src/multisig/index.ts new file mode 100644 index 00000000..5185e2f0 --- /dev/null +++ b/src/multisig/index.ts @@ -0,0 +1,3 @@ +export * from "./multisigTransactionsFactory"; +export * from "./proposeTransferExecuteContract"; +export * from "./resources"; diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index e177b179..05f4c1e2 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -30,12 +30,10 @@ describe("test multisig transactions factory", function () { it("should create transaction for deploy multisig contract", function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - const boardMemberAddress = Address.newFromBech32( - "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - ); - const proposerAddress = Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + const boardMemberOne = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const boardMemberTwo = Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); - const board = [boardMemberAddress, proposerAddress]; + const board = [boardMemberOne, boardMemberTwo]; const amount = 1000000000000000000n; // 1 EGLD const transaction = factory.createTransactionForMultisigDeploy(senderAddress, { @@ -61,16 +59,14 @@ describe("test multisig transactions factory", function () { it("should create transaction for propose add board member", function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - const boardMemberAddress = Address.newFromBech32( - "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - ); + const boardMember = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); const transaction = factory.createTransactionForProposeAddBoardMember(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, - boardMemberAddress: boardMemberAddress, + boardMember: boardMember, }); assert.instanceOf(transaction, Transaction); @@ -87,14 +83,14 @@ describe("test multisig transactions factory", function () { it("should create transaction for propose add proposer", function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - const proposerAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const proposer = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); const transaction = factory.createTransactionForProposeAddProposer(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, - proposerAddress: proposerAddress, + proposer: proposer, }); assert.instanceOf(transaction, Transaction); @@ -161,7 +157,7 @@ describe("test multisig transactions factory", function () { const transaction = factory.createTransactionForProposeTransferExecute(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, - egldAmount: amount, + nativeTokenAmount: amount, to: destinationContract, functionName: "add", functionArguments: [7], @@ -253,7 +249,7 @@ describe("test multisig transactions factory", function () { const transaction = factory.createTransactionForDeposit(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, - egldAmount: 1n, + nativeTokenAmount: 1n, tokenTransfers: [], }); @@ -280,7 +276,7 @@ describe("test multisig transactions factory", function () { const transaction = factory.createTransactionForDeposit(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, - egldAmount: 0n, + nativeTokenAmount: 0n, tokenTransfers: [tokenTransfer], }); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 9bd5ac0c..e564b0e5 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -2,7 +2,6 @@ import { AddressValue, ArgSerializer, BigUIntValue, - ContractFunction, EndpointDefinition, EndpointModifiers, NativeSerializer, @@ -19,13 +18,14 @@ import { Address } from "../core/address"; import { Transaction } from "../core/transaction"; import { TransactionBuilder } from "../core/transactionBuilder"; import { SmartContractTransactionsFactory } from "../smartContracts"; +import { ProposeTransferExecuteContractInput } from "./proposeTransferExecuteContract"; import * as resources from "./resources"; interface IAbi { constructorDefinition: EndpointDefinition; upgradeConstructorDefinition?: EndpointDefinition; - getEndpoint(name: string | ContractFunction): EndpointDefinition; + getEndpoint(name: string): EndpointDefinition; } /** * Use this class to create multisig related transactions like creating a new multisig contract, @@ -68,7 +68,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor ): Transaction { const dataParts = [ "proposeAddBoardMember", - this.argSerializer.valuesToStrings([new AddressValue(options.boardMemberAddress)])[0], + this.argSerializer.valuesToStrings([new AddressValue(options.boardMember)])[0], ]; return new TransactionBuilder({ @@ -77,7 +77,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -87,7 +87,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor createTransactionForProposeAddProposer(sender: Address, options: resources.ProposeAddProposerInput): Transaction { const dataParts = [ "proposeAddProposer", - this.argSerializer.valuesToStrings([new AddressValue(options.proposerAddress)])[0], + this.argSerializer.valuesToStrings([new AddressValue(options.proposer)])[0], ]; return new TransactionBuilder({ @@ -96,7 +96,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -115,7 +115,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -134,7 +134,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -146,19 +146,20 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor options: resources.ProposeTransferExecuteInput, ): Transaction { const gasOption = new U64Value(options.gasLimit); - const input = resources.ProposeTransferExecutInput.newFromTransferExecuteInput({ + const input = ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ multisig: options.multisigContract, to: options.to, - tokenTransfers: [], functionName: options.functionName, arguments: options.functionArguments, abi: options.abi, }); const dataParts = [ "proposeTransferExecute", - this.argSerializer.valuesToStrings([new AddressValue(options.to)])[0], - this.argSerializer.valuesToStrings([new BigUIntValue(options.egldAmount)])[0], - this.argSerializer.valuesToStrings([new OptionValue(new OptionType(new U64Type()), gasOption)])[0], + ...this.argSerializer.valuesToStrings([ + new AddressValue(options.to), + new BigUIntValue(options.nativeTokenAmount), + new OptionValue(new OptionType(new U64Type()), gasOption), + ]), ...input.functionCall, ]; @@ -168,7 +169,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -181,7 +182,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor function: "deposit", gasLimit: options.gasLimit ?? 0n, arguments: [], - nativeTransferAmount: options.egldAmount, + nativeTransferAmount: options.nativeTokenAmount, tokenTransfers: options.tokenTransfers, }); } @@ -193,10 +194,9 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor sender: Address, options: resources.ProposeTransferExecuteEsdtInput, ): Transaction { - const input = resources.ProposeTransferExecutInput.newFromTransferExecuteInput({ + const input = ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ multisig: options.multisigContract, to: options.to, - tokenTransfers: [], functionName: options.functionName, arguments: options.functionArguments, abi: options.abi, @@ -228,7 +228,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -236,7 +236,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor * Proposes an async call to another contract */ createTransactionForProposeAsyncCall(sender: Address, options: resources.ProposeAsyncCallInput): Transaction { - const input = resources.ProposeTransferExecutInput.newFromTransferExecuteInput({ + const input = ProposeTransferExecuteContractInput.newFromProposeAsyncCallInput({ multisig: options.multisigContract, to: options.to, tokenTransfers: options.tokenTransfers, @@ -247,9 +247,11 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor let receiver = options.multisigContract; const dataParts = [ "proposeAsyncCall", - this.argSerializer.valuesToStrings([new AddressValue(options.to)])[0], - this.argSerializer.valuesToStrings([new BigUIntValue(options.nativeTransferAmount)])[0], - this.argSerializer.valuesToStrings([new BigUIntValue(options.gasLimit ?? 0n)])[0], + ...this.argSerializer.valuesToStrings([ + new AddressValue(options.to), + new BigUIntValue(options.nativeTransferAmount), + new BigUIntValue(options.gasLimit ?? 0n), + ]), ...input.functionCall, ]; @@ -273,8 +275,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor ): Transaction { const dataParts = [ "proposeSCDeployFromSource", - this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0], - this.argSerializer.valuesToStrings([new AddressValue(options.source)])[0], + ...this.argSerializer.valuesToStrings([new BigUIntValue(options.amount), new AddressValue(options.source)]), options.codeMetadata.toString(), ...options.arguments, ]; @@ -285,7 +286,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -298,9 +299,11 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor ): Transaction { const dataParts = [ "proposeSCUpgradeFromSource", - this.argSerializer.valuesToStrings([new AddressValue(options.scAddress)])[0], - this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0], - this.argSerializer.valuesToStrings([new AddressValue(options.source)])[0], + ...this.argSerializer.valuesToStrings([ + new AddressValue(options.scAddress), + new BigUIntValue(options.amount), + new AddressValue(options.source), + ]), options.codeMetadata.toString(), ...options.arguments, ]; @@ -311,7 +314,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -327,7 +330,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -343,7 +346,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -359,7 +362,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -378,7 +381,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -394,7 +397,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -410,7 +413,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -437,7 +440,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -453,7 +456,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -469,7 +472,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -485,7 +488,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } @@ -503,7 +506,7 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor receiver: options.multisigContract, dataParts: dataParts, gasLimit: options.gasLimit, - addDataMovementGas: true, + addDataMovementGas: false, }).build(); } } diff --git a/src/multisig/proposeTransferExecuteContract.ts b/src/multisig/proposeTransferExecuteContract.ts new file mode 100644 index 00000000..c6fcb78b --- /dev/null +++ b/src/multisig/proposeTransferExecuteContract.ts @@ -0,0 +1,90 @@ +import { Abi, BytesValue } from "../abi"; +import { Address, TokenTransfer, TransactionsFactoryConfig } from "../core"; +import { ARGUMENTS_SEPARATOR } from "../core/constants"; +import { utf8ToHex } from "../core/utils.codec"; +import { SmartContractTransactionsFactory } from "../smartContracts"; + +export class ProposeTransferExecuteContractInput { + multisigContract: Address; + to: Address; + gasLimit?: bigint; + functionCall: any[]; + + constructor(options: { multisigContract: Address; to: Address; gasLimit?: bigint; functionCall: any[] }) { + this.multisigContract = options.multisigContract; + this.to = options.to; + this.gasLimit = options.gasLimit; + this.functionCall = options.functionCall; + } + + static newFromTransferExecuteInput(options: { + multisig: Address; + to: Address; + functionName: string; + arguments: any[]; + optGasLimit?: bigint; + abi?: Abi; + }): ProposeTransferExecuteContractInput { + const transactionsFactory = new SmartContractTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: "" }), + abi: options.abi, + }); + const transaction = transactionsFactory.createTransactionForExecute(Address.empty(), { + contract: Address.empty(), + function: options.functionName, + gasLimit: 0n, + arguments: options.arguments, + nativeTransferAmount: 0n, + }); + const functionCallParts = Buffer.from(transaction.data).toString().split(ARGUMENTS_SEPARATOR); + const functionName = functionCallParts[0]; + const functionArguments = []; + for (let index = 1; index < functionCallParts.length; index++) { + const element = functionCallParts[index]; + functionArguments.push(element.valueOf()); + } + const functionCall = [new BytesValue(Buffer.from(utf8ToHex(functionName))), ...functionArguments]; + return new ProposeTransferExecuteContractInput({ + multisigContract: options.multisig, + to: options.to, + functionCall: functionCall, + gasLimit: options.optGasLimit, + }); + } + + static newFromProposeAsyncCallInput(options: { + multisig: Address; + to: Address; + tokenTransfers: TokenTransfer[]; + functionName: string; + arguments: any[]; + optGasLimit?: bigint; + abi?: Abi; + }): ProposeTransferExecuteContractInput { + const transactionsFactory = new SmartContractTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: "" }), + abi: options.abi, + }); + const transaction = transactionsFactory.createTransactionForExecute(Address.empty(), { + contract: Address.empty(), + function: options.functionName, + gasLimit: 0n, + arguments: options.arguments, + nativeTransferAmount: 0n, + }); + const functionCallParts = Buffer.from(transaction.data).toString().split(ARGUMENTS_SEPARATOR); + const functionName = functionCallParts[0]; + const functionArguments = []; + for (let index = 1; index < functionCallParts.length; index++) { + const element = functionCallParts[index]; + functionArguments.push(element.valueOf()); + } + const functionCall = [new BytesValue(Buffer.from(utf8ToHex(functionName))), ...functionArguments]; + return new ProposeTransferExecuteContractInput({ + multisigContract: options.multisig, + to: options.to, + functionCall: functionCall, + gasLimit: options.optGasLimit, + }); + } +} diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 45a09d59..546fde6c 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -1,10 +1,7 @@ -import { Abi, BytesValue } from "../abi"; -import { Token, TokenTransfer, TransactionsFactoryConfig } from "../core"; +import { Abi } from "../abi"; +import { Token, TokenTransfer } from "../core"; import { Address } from "../core/address"; import { CodeMetadata } from "../core/codeMetadata"; -import { ARGUMENTS_SEPARATOR } from "../core/constants"; -import { utf8ToHex } from "../core/utils.codec"; -import { SmartContractTransactionsFactory } from "../smartContracts"; export type DeployMultisigContractInput = { quorum: number; @@ -24,11 +21,11 @@ export type MultisigContractInput = { }; export type ProposeAddBoardMemberInput = MultisigContractInput & { - boardMemberAddress: Address; + boardMember: Address; }; export type ProposeAddProposerInput = MultisigContractInput & { - proposerAddress: Address; + proposer: Address; }; export type ProposeRemoveUserInput = MultisigContractInput & { @@ -41,157 +38,29 @@ export type ProposeChangeQuorumInput = MultisigContractInput & { export type ProposeTransferExecuteInput = MultisigContractInput & { to: Address; - egldAmount: bigint; - gasLimit?: bigint; + nativeTokenAmount: bigint; + optGasLimit?: bigint; functionName: string; functionArguments: any[]; abi?: Abi; }; export type DepositExecuteInput = MultisigContractInput & { - egldAmount: bigint; + nativeTokenAmount: bigint; gasLimit?: bigint; tokenTransfers: TokenTransfer[]; }; -export class ProposeTransferExecuteEsdtInput { - multisigContract: Address; +export type ProposeTransferExecuteEsdtInput = MultisigContractInput & { to: Address; tokens: any[]; - gasLimit: bigint; + optGasLimit?: bigint; functionName: string; functionArguments: any[]; abi?: Abi; +}; - constructor(options: ProposeTransferExecuteEsdtInput) { - this.multisigContract = options.multisigContract; - this.to = options.to; - this.tokens = options.tokens; - this.gasLimit = options.gasLimit; - this.functionName = options.functionName; - this.functionArguments = options.functionArguments; - this.abi = options.abi; - } -} - -export class ProposeTransferExecuteEsdtInputForContract { - multisigContract: Address; - to: Address; - gasLimit?: bigint; - functionCall: any[]; - tokens: EsdtTokenPayment[]; - - constructor(options: { - multisigContract: Address; - to: Address; - gasLimit?: bigint; - functionCall: any[]; - tokens: EsdtTokenPayment[]; - }) { - this.multisigContract = options.multisigContract; - this.to = options.to; - this.gasLimit = options.gasLimit; - this.functionCall = options.functionCall; - this.tokens = options.tokens; - } - - static newFromTransferExecuteInput(options: { - multisig: Address; - to: Address; - nativeTransferAmount: bigint; - tokenTransfers: TokenTransfer[]; - functionName: string; - arguments: any[]; - optGasLimit?: bigint; - abi?: Abi; - }): ProposeTransferExecuteEsdtInputForContract { - const transactionsFactory = new SmartContractTransactionsFactory({ - config: new TransactionsFactoryConfig({ chainID: "" }), - abi: options.abi, - }); - const transaction = transactionsFactory.createTransactionForExecute(Address.empty(), { - contract: Address.empty(), - function: options.functionName, - gasLimit: 0n, - arguments: options.arguments, - nativeTransferAmount: 0n, - tokenTransfers: options.tokenTransfers, - }); - - const tokens: EsdtTokenPayment[] = options.tokenTransfers.map((token) => { - return { token_identifier: token.token.identifier, token_nonce: token.token.nonce, amount: token.amount }; - }); - - const functionCallParts = Buffer.from(transaction.data).toString().split(ARGUMENTS_SEPARATOR); - const functionName = functionCallParts[0]; - const functionArguments = []; - for (let index = 1; index < functionCallParts.length; index++) { - const element = functionCallParts[index]; - functionArguments.push(element.valueOf()); - } - const functionCall = [new BytesValue(Buffer.from(utf8ToHex(functionName))), ...functionArguments]; - return new ProposeTransferExecuteEsdtInputForContract({ - multisigContract: options.multisig, - to: options.to, - functionCall: functionCall, - gasLimit: options.optGasLimit, - tokens: tokens, - }); - } -} - -export class ProposeTransferExecutInput { - multisigContract: Address; - to: Address; - gasLimit?: bigint; - functionCall: any[]; - - constructor(options: { multisigContract: Address; to: Address; gasLimit?: bigint; functionCall: any[] }) { - this.multisigContract = options.multisigContract; - this.to = options.to; - this.gasLimit = options.gasLimit; - this.functionCall = options.functionCall; - } - - static newFromTransferExecuteInput(options: { - multisig: Address; - to: Address; - tokenTransfers: TokenTransfer[]; - functionName: string; - arguments: any[]; - optGasLimit?: bigint; - abi?: Abi; - }): ProposeTransferExecutInput { - const transactionsFactory = new SmartContractTransactionsFactory({ - config: new TransactionsFactoryConfig({ chainID: "" }), - abi: options.abi, - }); - const transaction = transactionsFactory.createTransactionForExecute(Address.empty(), { - contract: Address.empty(), - function: options.functionName, - gasLimit: 0n, - arguments: options.arguments, - nativeTransferAmount: 0n, - tokenTransfers: options.tokenTransfers, - }); - const functionCallParts = Buffer.from(transaction.data).toString().split(ARGUMENTS_SEPARATOR); - const functionName = functionCallParts[0]; - const functionArguments = []; - for (let index = 1; index < functionCallParts.length; index++) { - const element = functionCallParts[index]; - functionArguments.push(element.valueOf()); - } - const functionCall = [new BytesValue(Buffer.from(utf8ToHex(functionName))), ...functionArguments]; - return new ProposeTransferExecutInput({ - multisigContract: options.multisig, - to: options.to, - functionCall: functionCall, - gasLimit: options.optGasLimit, - }); - } -} - -export class ProposeAsyncCallInput { +export type ProposeAsyncCallInput = MultisigContractInput & { multisigContract: Address; to: Address; nativeTransferAmount: bigint; @@ -200,26 +69,7 @@ export class ProposeAsyncCallInput { functionArguments: any[]; gasLimit: bigint; abi?: Abi; - constructor(options: { - multisigContract: Address; - to: Address; - nativeTransferAmount: bigint; - tokenTransfers: TokenTransfer[]; - functionName: string; - functionArguments: any[]; - gasLimit: bigint; - abi?: Abi; - }) { - this.multisigContract = options.multisigContract; - this.to = options.to; - this.nativeTransferAmount = options.nativeTransferAmount; - this.tokenTransfers = options.tokenTransfers; - this.functionName = options.functionName; - this.functionArguments = options.functionArguments; - this.gasLimit = options.gasLimit; - this.abi = options.abi; - } -} +}; export type ProposeSCDeployFromSourceInput = MultisigContractInput & { amount: bigint; From 1b0782d6353fe60993c33ca8c23d6c47542e7db9 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 15:29:46 +0300 Subject: [PATCH 04/14] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12a2eb43..cf06e411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.0.2", + "version": "14.1.0-beta.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.0.2", + "version": "14.1.0-beta.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 1e5d2d8b..ddc442e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.0.2", + "version": "14.1.0-beta.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From baa7983c3389f9eb765186481aec65f800d0e3d7 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 12:30:12 +0300 Subject: [PATCH 05/14] Add multisig controller --- src/multisig/multisigController.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 03def853..79c9855b 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -499,8 +499,9 @@ export class MultisigController extends SmartContractController { /** * Awaits the completion of a propose transfer execute action */ - async awaitCompletedDepositExecute(txHash: string): Promise { - await this.transactionAwaiter.awaitCompleted(txHash); + async awaitCompletedDepositExecute(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parseActionProposal(transaction); } /** From 1fa8802eeb10d96459abe44e322e6b043c1506a0 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 16 Apr 2025 14:09:30 +0300 Subject: [PATCH 06/14] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf06e411..877e8a78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.0", + "version": "14.1.0-beta.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.0", + "version": "14.1.0-beta.1", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index ddc442e9..90ff967e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.0", + "version": "14.1.0-beta.1", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From 2bcc31c66c480067756bb60e182209ed10516337 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 17 Apr 2025 16:04:27 +0300 Subject: [PATCH 07/14] Add getPendingActionFullInfo --- src/multisig/multisigController.spec.ts | 52 +++++++++++++++++++++++++ src/multisig/multisigController.ts | 26 +++++++++++++ src/multisig/resources.ts | 8 ++++ 3 files changed, 86 insertions(+) diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts index 45bace69..28dea289 100644 --- a/src/multisig/multisigController.spec.ts +++ b/src/multisig/multisigController.spec.ts @@ -311,6 +311,58 @@ describe("test multisig controller query methods", () => { assert.equal(mappedRes.amount, 42n); }); + it("getPendingActionFullInfo returns all the actions pending", async function () { + networkProvider.mockQueryContractOnFunction( + "getPendingActionFullInfo", + new SmartContractQueryResponse({ + function: "getPendingActionFullInfo", + returnDataParts: [ + Buffer.from( + "AAAAAQAAAAAFgEnWOeWmmA0c0jkqvM5BApzadKFWNSOiAvCWQcwmGPgAAAAIDeC2s6dkAAABAAAAAAF9eEAAAAAAAAAAAAAAAAEBOUcu/2iGdxqYLzCD2l1CHyTCkYHmOIgijcgcpg1p4Q==", + "base64", + ), + Buffer.from( + "AAAAAgAAAAAHAAAAAAAAAAAFAHjSljKssVmYAD9hXQpRJhNT2AQdPhMAAAAIDeC2s6dkAAABAAAAAAOThwAAAAABCgAAAAIAAAABDQAAAAENAAAAAQE5Ry7/aIZ3GpgvMIPaXUIfJMKRgeY4iCKNyBymDWnh", + "base64", + ), + Buffer.from( + "AAAAAwAAAAAGAAAAAAAAAAAFAEm/+WO9+j6gJxM2IJXfMuPXCOrM/FcAAAABAAAADEFMSUNFLTU2MjdmMQAAAAAAAAAAAAAAAAEAAAAAAExLQAAAABQ2NDY5NzM3NDcyNjk2Mjc1NzQ2NQAAAAAAAAABATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", + "base64", + ), + Buffer.from( + "AAAABAAAAAAGAAAAAAAAAAAFAEm/+WO9+j6gJxM2IJXfMuPXCOrM/FcAAAABAAAADEFMSUNFLTU2MjdmMQAAAAAAAAAAAAAAAQoBAAAAAABMS0AAAAAUNjQ2OTczNzQ3MjY5NjI3NTc0NjUAAAAAAAAAAQE5Ry7/aIZ3GpgvMIPaXUIfJMKRgeY4iCKNyBymDWnh", + "base64", + ), + Buffer.from( + "AAAABgAAAAACgEnWOeWmmA0c0jkqvM5BApzadKFWNSOiAvCWQcwmGPgAAAABATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", + "base64", + ), + Buffer.from( + "AAAABwAAAAAIAAAAB7GivC7FAAAAAAAAAAAAAAUAhw0EEs7ehxhTocLUinVDwHPrOflp4QUAAAAAAQAAAAEHAAAAAQE5Ry7/aIZ3GpgvMIPaXUIfJMKRgeY4iCKNyBymDWnh", + "base64", + ), + Buffer.from( + "AAAACAAAAAAJAAAAAAAAAAAFAH4lzm3rrHSNhrXTkxIKsesCpG1YFnkAAAAHsaK8LsUAAAAAAAAAAAAABQBqvRw6N5TaAWArhVrAPngh5mOOyBZ5BQAAAAAAAAAAAQE5Ry7/aIZ3GpgvMIPaXUIfJMKRgeY4iCKNyBymDWnh", + "base64", + ), + Buffer.from("AAAACQAAAAAEAAAAAgAAAAEBOUcu/2iGdxqYLzCD2l1CHyTCkYHmOIgijcgcpg1p4Q==", "base64"), + Buffer.from( + "AAAACgAAAAADgEnWOeWmmA0c0jkqvM5BApzadKFWNSOiAvCWQcwmGPgAAAABATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", + "base64", + ), + ], + returnCode: "ok", + returnMessage: "ok", + }), + ); + + const result = await controller.getPendingActionFullInfo({ + mutisigAddress: mockMultisigAddress, + }); + + assert.equal(result.length, 9); + }); + // TODO: I'll do this on a future branch it.skip("getActionData returns the action data as SendAsyncCall", async function () { networkProvider.mockQueryContractOnFunction( diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 79c9855b..cfd060ba 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -223,6 +223,31 @@ export class MultisigController extends SmartContractController { return result; } + /** + * Gets all pending actions. + */ + async getPendingActionFullInfo(options: { mutisigAddress: string }): Promise { + const response = await this.query({ + contract: Address.newFromBech32(options.mutisigAddress), + function: "getPendingActionFullInfo", + arguments: [], + }); + + const result: resources.FullMultisigAction[] = []; + const actions = response[0]; + for (let action = 0; action < actions.length; action++) { + const element = actions[action]; + console.log({ element }, element.action_id, this.mapResponseToAction(element.action_data.valueOf())); + result.push({ + actionId: Number(element.action_id.toString()), + groupId: Number(element.group_id.toString()), + actionData: this.mapResponseToAction(element.action_data.valueOf()), + signers: element.signers.map((address: Address) => new Address(address).toBech32()), + }); + } + return result; + } + /** * 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. @@ -802,6 +827,7 @@ export class MultisigController extends SmartContractController { case resources.MultisigActionEnum.SCUpgradeFromSource: return new resources.SCUpgradeFromSource(fields); default: + console; throw new Error(`Unknown action type: ${name}`); } }; diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 4fae9572..d4ce70cf 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -123,6 +123,14 @@ export enum MultisigActionEnum { export class MultisigAction { public type: MultisigActionEnum = MultisigActionEnum.Nothing; } + +export type FullMultisigAction = { + actionId: number; + groupId: number; + signers: Address[]; + actionData: MultisigAction; +}; + export class AddBoardMember extends MultisigAction { public address: Address; constructor(address: Address) { From 7b02b905ddba225a3c63dbbf436e17b51b1cf949 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 24 Apr 2025 08:06:07 +0300 Subject: [PATCH 08/14] Update getAllBoardMembers mapping --- src/multisig/multisigController.spec.ts | 16 ++++++++-------- src/multisig/multisigController.ts | 12 +++++------- src/multisig/resources.ts | 20 ++++++++++++++------ 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts index 28dea289..ad448acf 100644 --- a/src/multisig/multisigController.spec.ts +++ b/src/multisig/multisigController.spec.ts @@ -239,14 +239,14 @@ describe("test multisig controller query methods", () => { }); it("getAllBoardMembers returns all board members as address array", async function () { - // Prepare addresses for the mock response - const address1 = Buffer.from(Address.newFromBech32(mockBoardMemberAddress).toHex(), "hex"); - const address2 = Buffer.from(Address.newFromBech32(mockProposerAddress).toHex(), "hex"); networkProvider.mockQueryContractOnFunction( "getAllBoardMembers", new SmartContractQueryResponse({ function: "getAllBoardMembers", - returnDataParts: [address1, address2], + returnDataParts: [ + Buffer.from("ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", "base64"), + Buffer.from("gEnWOeWmmA0c0jkqvM5BApzadKFWNSOiAvCWQcwmGPg=", "base64"), + ], returnCode: "ok", returnMessage: "ok", }), @@ -257,8 +257,8 @@ describe("test multisig controller query methods", () => { }); assert.equal(result.length, 2); - assert.equal(result[0], mockBoardMemberAddress); - assert.equal(result[1], mockProposerAddress); + assert.equal(result[0], "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(result[1], "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); }); it("getAllProposers returns all proposers as address array", async function () { @@ -484,7 +484,7 @@ describe("test multisig controller query methods", () => { const mappedRes = result as resources.SCDeployFromSource; assert.equal( - mappedRes.sourceContractAddress.toBech32(), + mappedRes.sourceContract.toBech32(), "erd1qqqqqqqqqqqqqpgqsuxsgykwm6r3s5apct2g5a2rcpe7kw0ed8ssf6h9f6", ); assert.equal(mappedRes.amount.toString(), "50000000000000000"); @@ -516,7 +516,7 @@ describe("test multisig controller query methods", () => { }); const mappedRes = result as resources.SCUpgradeFromSource; - assert.equal(mappedRes.sourceContractAddress.toBech32(), sourceContract.toBech32()); + assert.equal(mappedRes.sourceContract.toBech32(), sourceContract.toBech32()); assert.equal(mappedRes.amount, amount); assert.deepEqual(mappedRes.codeMetadata, metadata); }); diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index cfd060ba..ce837740 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -83,7 +83,7 @@ export class MultisigController extends SmartContractController { arguments: [], }); - return Number(Buffer.from(response[0]).toString()); + return Number(response[0].toString()); } /** @@ -96,7 +96,7 @@ export class MultisigController extends SmartContractController { arguments: [], }); - return Number(Buffer.from(response[0]).toString()); + return Number(response[0].toString()); } /** @@ -179,7 +179,7 @@ export class MultisigController extends SmartContractController { arguments: [], }); - return response[0].map((address: Address) => address.toBech32()); + return response[0].map((address: Address) => address?.toBech32()); } /** @@ -192,7 +192,7 @@ export class MultisigController extends SmartContractController { arguments: [], }); - return response[0].map((address: Address) => new Address(address).toBech32()); + return response[0].map((address: Address) => address?.toBech32()); } /** * "Indicates user rights.", @@ -237,7 +237,6 @@ export class MultisigController extends SmartContractController { const actions = response[0]; for (let action = 0; action < actions.length; action++) { const element = actions[action]; - console.log({ element }, element.action_id, this.mapResponseToAction(element.action_data.valueOf())); result.push({ actionId: Number(element.action_id.toString()), groupId: Number(element.group_id.toString()), @@ -287,7 +286,7 @@ export class MultisigController extends SmartContractController { arguments: [options.actionId], }); - return Number(Buffer.from(response[0]).toString()); + return Number(response[0].toString()); } /** @@ -827,7 +826,6 @@ export class MultisigController extends SmartContractController { case resources.MultisigActionEnum.SCUpgradeFromSource: return new resources.SCUpgradeFromSource(fields); default: - console; throw new Error(`Unknown action type: ${name}`); } }; diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index d4ce70cf..c1e3aa9d 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -141,6 +141,7 @@ export class AddBoardMember extends MultisigAction { } export class AddProposer extends MultisigAction { public address: Address; + constructor(address: Address) { super(); this.type = MultisigActionEnum.AddProposer; @@ -150,6 +151,7 @@ export class AddProposer extends MultisigAction { export class RemoveUser extends MultisigAction { public type: MultisigActionEnum = MultisigActionEnum.RemoveUser; public address: Address; + constructor(address: Address) { super(); this.type = MultisigActionEnum.RemoveUser; @@ -159,6 +161,7 @@ export class RemoveUser extends MultisigAction { export class ChangeQuorum extends MultisigAction { public quorum: number; + constructor(quorum: number) { super(); this.type = MultisigActionEnum.ChangeQuorum; @@ -172,6 +175,7 @@ export class SendTransferExecuteEgld extends MultisigAction { optionalGasLimit?: number; funcionName: string; arguments: Uint8Array[]; + constructor(data: any) { super(); this.type = MultisigActionEnum.SendTransferExecuteEgld; @@ -188,6 +192,7 @@ export class SendTransferExecuteEsdt extends MultisigAction { optionalGasLimit?: number; funcionName: string; arguments: Uint8Array[]; + constructor(data: any) { super(); this.type = MultisigActionEnum.SendTransferExecuteEsdt; @@ -212,6 +217,7 @@ export class SendAsyncCall extends MultisigAction { optionalGasLimit?: number; funcionName: string; arguments: Uint8Array[]; + constructor(data: any) { super(); this.type = MultisigActionEnum.SendAsyncCall; @@ -224,14 +230,15 @@ export class SendAsyncCall extends MultisigAction { } export class SCDeployFromSource extends MultisigAction { - sourceContractAddress: Address; + sourceContract: Address; amount: bigint; codeMetadata: CodeMetadata; arguments: Uint8Array[]; + constructor(data: any) { super(); this.type = MultisigActionEnum.SCDeployFromSource; - this.sourceContractAddress = data[1]; + this.sourceContract = data[1]; this.amount = data[0]; this.codeMetadata = data[2]; this.arguments = data[3]; @@ -239,17 +246,18 @@ export class SCDeployFromSource extends MultisigAction { } export class SCUpgradeFromSource extends MultisigAction { - sourceContractAddress: Address; - scAddress: Address; + sourceContract: Address; + destinationContract: Address; amount: bigint; codeMetadata: CodeMetadata; arguments: Uint8Array[]; + constructor(data: any) { super(); this.type = MultisigActionEnum.SCUpgradeFromSource; - this.scAddress = data[0]; + this.destinationContract = data[0]; this.amount = data[1]; - this.sourceContractAddress = data[2]; + this.sourceContract = data[2]; this.codeMetadata = data[3]; this.arguments = data[4]; } From 6ff152e02427f8193d6cad5d291d06168efac0f5 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 5 May 2025 11:41:42 +0300 Subject: [PATCH 09/14] Update multisig parser --- src/multisig/multisigController.ts | 3 +- .../multisigTransactionsOutcomeParser.ts | 218 ++---------------- 2 files changed, 17 insertions(+), 204 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index ce837740..6a6debff 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -13,6 +13,7 @@ import { SmartContractController } from "../smartContracts"; import { MultisigTransactionsFactory } from "./multisigTransactionsFactory"; import { MultisigTransactionsOutcomeParser } from "./multisigTransactionsOutcomeParser"; import * as resources from "./resources"; + export class MultisigController extends SmartContractController { private transactionAwaiter: TransactionWatcher; private multisigFactory: MultisigTransactionsFactory; @@ -26,7 +27,7 @@ export class MultisigController extends SmartContractController { config: new TransactionsFactoryConfig({ chainID: options.chainID }), abi: options.abi, }); - this.multisigParser = new MultisigTransactionsOutcomeParser(); + this.multisigParser = new MultisigTransactionsOutcomeParser({ abi: options.abi }); } /** diff --git a/src/multisig/multisigTransactionsOutcomeParser.ts b/src/multisig/multisigTransactionsOutcomeParser.ts index 28c100d2..ec06c426 100644 --- a/src/multisig/multisigTransactionsOutcomeParser.ts +++ b/src/multisig/multisigTransactionsOutcomeParser.ts @@ -1,43 +1,29 @@ +import { Abi } from "../abi"; import { TransactionOnNetwork } from "../core"; import { Address } from "../core/address"; import { Err } from "../core/errors"; -import { TransactionEvent } from "../core/transactionEvents"; -import { SmartContractCallOutcome, SmartContractResult } from "../transactionsOutcomeParsers/resources"; - -enum Events { - MultisigDeploy = "MultisigDeploy", - MultisigActionProp = "MultisigActionProposal", - SignalError = "signalError", - WriteLog = "writeLog", -} +import { SmartContractDeployOutcome } from "../smartContracts/resources"; +import { SmartContractTransactionsOutcomeParser } from "../transactionsOutcomeParsers"; /** * Parses the outcome of multisig contract operations */ export class MultisigTransactionsOutcomeParser { + private parser: SmartContractTransactionsOutcomeParser; + private readonly abi?: Abi; + + constructor(options?: { abi?: Abi }) { + this.abi = options?.abi; + this.parser = new SmartContractTransactionsOutcomeParser({ abi: this.abi }); + } + /** * Parses the outcome of creating a new multisig contract * @param transactionOnNetwork The completed transaction * @returns An array of objects containing the new contract addresses */ - parseDeployMultisigContract(transactionOnNetwork: TransactionOnNetwork): { contractAddress: Address }[] { - const directCallOutcome = this.findDirectMultisigDeployOutcome(transactionOnNetwork); - - if (!directCallOutcome || directCallOutcome.returnCode !== "ok") { - return []; - } - - // Look for the deployment events - const events = transactionOnNetwork.logs.events - .concat(transactionOnNetwork.smartContractResults.flatMap((result) => result.logs.events)) - .filter((event) => event.identifier === Events.MultisigDeploy); - - return events.map((event) => { - // Assuming the contract address is in the first topic - const addressBytes = Buffer.from(event.topics[0]); - const address = Address.newFromHex(addressBytes.toString("hex")); - return { contractAddress: address }; - }); + parseDeployMultisigContract(transactionOnNetwork: TransactionOnNetwork): SmartContractDeployOutcome { + return this.parser.parseDeploy({ transactionOnNetwork }); } /** @@ -46,183 +32,9 @@ export class MultisigTransactionsOutcomeParser { * @returns The action ID that was created */ parseActionProposal(transactionOnNetwork: TransactionOnNetwork): number { - const directCallOutcome = this.findDirectMultisigCallOutcome(transactionOnNetwork); + const result = this.parser.parseExecute({ transactionOnNetwork }); - if (!directCallOutcome || directCallOutcome.returnCode !== "ok") { - throw new Err("Failed to propose action: " + directCallOutcome.returnMessage); - } - - if (directCallOutcome.returnDataParts.length === 0) { - throw new Err("No action ID returned in the transaction outcome"); - } - - // Assuming the first return data part contains the action ID as bytes - const actionIdBytes = directCallOutcome.returnDataParts[0]; - return parseInt(Buffer.from(actionIdBytes).toString("hex"), 16); - } - - /** - * Finds the direct smart contract call outcome from a transaction - */ - protected findDirectMultisigCallOutcome(transactionOnNetwork: TransactionOnNetwork): SmartContractCallOutcome { - let outcome = this.findDirectMultisigCallOutcomeWithinSmartContractResults(transactionOnNetwork); - - if (outcome) { - return outcome; - } - - outcome = this.findDirectMultisigCallOutcomeIfError(transactionOnNetwork); - - if (outcome) { - return outcome; - } - - outcome = this.findDirectMultisigCallOutcomeWithinWriteLogEvents(transactionOnNetwork); - - if (outcome) { - return outcome; - } - - return new SmartContractCallOutcome({ - function: transactionOnNetwork.function, - returnCode: "", - returnMessage: "", - returnDataParts: [], - }); - } - - /** - * Similar to findDirectSmartContractCallOutcome but specifically for multisig deploy operations - */ - protected findDirectMultisigDeployOutcome(transactionOnNetwork: TransactionOnNetwork): SmartContractCallOutcome { - return this.findDirectMultisigCallOutcome(transactionOnNetwork); - } - - /** - * Finds the call outcome within smart contract results - */ - protected findDirectMultisigCallOutcomeWithinSmartContractResults( - transactionOnNetwork: TransactionOnNetwork, - ): SmartContractCallOutcome | null { - // Similar implementation to SmartContractTransactionsOutcomeParser but adapted for multisig - const eligibleResults: SmartContractResult[] = []; - - for (const result of transactionOnNetwork.smartContractResults) { - const matchesCriteria = - result.data.toString().startsWith("@") && - result.receiver.toBech32() === transactionOnNetwork.sender.toBech32(); - - if (matchesCriteria) { - eligibleResults.push(result); - } - } - - if (eligibleResults.length === 0) { - return null; - } - - if (eligibleResults.length > 1) { - throw new Error(`More than one smart contract result found for transaction: ${transactionOnNetwork.hash}`); - } - - const [result] = eligibleResults; - const parts = result.data.toString().split("@").filter(Boolean); - - let returnCode = "ok"; - let returnMessage = "success"; - - if (parts.length > 0) { - returnCode = Buffer.from(parts[0], "hex").toString() || "ok"; - } - - if (result.raw["returnMessage"]) { - returnMessage = result.raw["returnMessage"]; - } - - const returnDataParts = parts.slice(1).map((part) => Buffer.from(part, "hex")); - - return new SmartContractCallOutcome({ - function: transactionOnNetwork.function, - returnCode: returnCode, - returnMessage: returnMessage, - returnDataParts: returnDataParts, - }); - } - - /** - * Finds the call outcome if there was an error - */ - protected findDirectMultisigCallOutcomeIfError( - transactionOnNetwork: TransactionOnNetwork, - ): SmartContractCallOutcome | null { - const eventIdentifier = Events.SignalError; - const eligibleEvents: TransactionEvent[] = []; - - // First, look in "logs": - eligibleEvents.push( - ...transactionOnNetwork.logs.events.filter((event) => event.identifier === eventIdentifier), - ); - - // Then, look in "logs" of "contractResults": - for (const result of transactionOnNetwork.smartContractResults) { - eligibleEvents.push(...result.logs.events.filter((event) => event.identifier === eventIdentifier)); - } - - if (eligibleEvents.length === 0) { - return null; - } - - const [event] = eligibleEvents; - const lastTopic = event.topics[event.topics.length - 1]?.toString(); - - return new SmartContractCallOutcome({ - function: transactionOnNetwork.function, - returnCode: "error", - returnMessage: lastTopic || "Unknown error", - returnDataParts: [], - }); - } - - /** - * Finds the call outcome within write log events - */ - protected findDirectMultisigCallOutcomeWithinWriteLogEvents( - transactionOnNetwork: TransactionOnNetwork, - ): SmartContractCallOutcome | null { - const eventIdentifier = Events.WriteLog; - const eligibleEvents: TransactionEvent[] = []; - - // First, look in "logs": - eligibleEvents.push( - ...transactionOnNetwork.logs.events.filter((event) => event.identifier === eventIdentifier), - ); - - // Then, look in "logs" of "contractResults": - for (const result of transactionOnNetwork.smartContractResults) { - eligibleEvents.push(...result.logs.events.filter((event) => event.identifier === eventIdentifier)); - } - - if (eligibleEvents.length === 0) { - return null; - } - - const [event] = eligibleEvents; - const data = Buffer.from(event.data).toString(); - const parts = data.split("@").filter(Boolean); - - let returnCode = "ok"; - if (parts.length > 0) { - returnCode = Buffer.from(parts[0], "hex").toString() || "ok"; - } - - const returnDataParts = parts.slice(1).map((part) => Buffer.from(part, "hex")); - - return new SmartContractCallOutcome({ - function: transactionOnNetwork.function, - returnCode: returnCode, - returnMessage: returnCode, - returnDataParts: returnDataParts, - }); + return result.values[0]; } /** From 71ab609748f5daba4b5fcbdeb4ec9e13633beb8e Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 5 May 2025 16:00:40 +0300 Subject: [PATCH 10/14] Make abi mandatory --- src/multisig/multisigController.spec.ts | 5 +---- src/multisig/multisigController.ts | 2 +- src/multisig/multisigTransactionsFactory.ts | 2 +- src/multisig/multisigTransactionsOutcomeParser.ts | 6 +++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts index ad448acf..b61c4e2a 100644 --- a/src/multisig/multisigController.spec.ts +++ b/src/multisig/multisigController.spec.ts @@ -9,10 +9,7 @@ describe("test multisig controller query methods", () => { const mockBoardMemberAddress = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; const mockProposerAddress = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"; let networkProvider = new MockNetworkProvider(); - let controller = new MultisigController({ - chainID: "D", - networkProvider: networkProvider, - }); + let controller: MultisigController; beforeEach(async function () { networkProvider = new MockNetworkProvider(); diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 6a6debff..5054bc7c 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -19,7 +19,7 @@ export class MultisigController extends SmartContractController { private multisigFactory: MultisigTransactionsFactory; private multisigParser: MultisigTransactionsOutcomeParser; - constructor(options: { chainID: string; networkProvider: INetworkProvider; abi?: Abi }) { + constructor(options: { chainID: string; networkProvider: INetworkProvider; abi: Abi }) { super(options); this.abi = options.abi; this.transactionAwaiter = new TransactionWatcher(options.networkProvider); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index d0870a12..e7595d4c 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -28,7 +28,7 @@ import * as resources from "./resources"; export class MultisigTransactionsFactory extends SmartContractTransactionsFactory { private readonly argSerializer: ArgSerializer; - constructor(options: { config: TransactionsFactoryConfig; abi?: Abi }) { + constructor(options: { config: TransactionsFactoryConfig; abi: Abi }) { super(options); this.argSerializer = new ArgSerializer(); } diff --git a/src/multisig/multisigTransactionsOutcomeParser.ts b/src/multisig/multisigTransactionsOutcomeParser.ts index ec06c426..640bbe26 100644 --- a/src/multisig/multisigTransactionsOutcomeParser.ts +++ b/src/multisig/multisigTransactionsOutcomeParser.ts @@ -10,9 +10,9 @@ import { SmartContractTransactionsOutcomeParser } from "../transactionsOutcomePa */ export class MultisigTransactionsOutcomeParser { private parser: SmartContractTransactionsOutcomeParser; - private readonly abi?: Abi; + private readonly abi: Abi; - constructor(options?: { abi?: Abi }) { + constructor(options: { abi: Abi }) { this.abi = options?.abi; this.parser = new SmartContractTransactionsOutcomeParser({ abi: this.abi }); } @@ -70,7 +70,7 @@ export class MultisigTransactionsOutcomeParser { return queryResponse.map((item) => { const buffer = Buffer.from(item, "base64"); - return Address.newFromHex(buffer.toString("hex")); + return new Address(buffer); }); } From 124eb90d2ce3d82ad1bc3e634592c34541ed487a Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 6 May 2025 10:59:19 +0300 Subject: [PATCH 11/14] Clean-up parser --- src/multisig/multisigController.ts | 25 ++++---- .../multisigTransactionsOutcomeParser.ts | 60 +++---------------- 2 files changed, 22 insertions(+), 63 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 5054bc7c..f94cae5e 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -314,7 +314,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeAddBoardMember(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -341,7 +341,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeAddProposer(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -368,7 +368,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeRemoveUser(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -395,7 +395,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeChangeQuorum(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -446,8 +446,9 @@ export class MultisigController extends SmartContractController { /** * Awaits the completion of a perform action */ - async awaitCompletedPerformAction(txHash: string): Promise { - await this.transactionAwaiter.awaitCompleted(txHash); + async awaitCompletedPerformAction(txHash: string): Promise
{ + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.multisigParser.parsePerformAction(transaction); } /** @@ -526,7 +527,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedDepositExecute(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -553,7 +554,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeTransferExecute(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -583,7 +584,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeTransferExecuteEsdt(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -610,7 +611,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeAsyncCall(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -637,7 +638,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeSCDeployFromSource(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** @@ -667,7 +668,7 @@ export class MultisigController extends SmartContractController { */ async awaitCompletedProposeSCUpgradeFromSource(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseActionProposal(transaction); + return this.multisigParser.parseProposeAction(transaction); } /** diff --git a/src/multisig/multisigTransactionsOutcomeParser.ts b/src/multisig/multisigTransactionsOutcomeParser.ts index 640bbe26..d595bf4f 100644 --- a/src/multisig/multisigTransactionsOutcomeParser.ts +++ b/src/multisig/multisigTransactionsOutcomeParser.ts @@ -1,7 +1,5 @@ import { Abi } from "../abi"; -import { TransactionOnNetwork } from "../core"; -import { Address } from "../core/address"; -import { Err } from "../core/errors"; +import { Address, TransactionOnNetwork } from "../core"; import { SmartContractDeployOutcome } from "../smartContracts/resources"; import { SmartContractTransactionsOutcomeParser } from "../transactionsOutcomeParsers"; @@ -22,7 +20,7 @@ export class MultisigTransactionsOutcomeParser { * @param transactionOnNetwork The completed transaction * @returns An array of objects containing the new contract addresses */ - parseDeployMultisigContract(transactionOnNetwork: TransactionOnNetwork): SmartContractDeployOutcome { + parseDeploy(transactionOnNetwork: TransactionOnNetwork): SmartContractDeployOutcome { return this.parser.parseDeploy({ transactionOnNetwork }); } @@ -31,60 +29,20 @@ export class MultisigTransactionsOutcomeParser { * @param transactionOnNetwork The completed transaction * @returns The action ID that was created */ - parseActionProposal(transactionOnNetwork: TransactionOnNetwork): number { + parseProposeAction(transactionOnNetwork: TransactionOnNetwork): number { const result = this.parser.parseExecute({ transactionOnNetwork }); return result.values[0]; } /** - * Parses the outcome of a query to get the multisig contract's pending actions - * @param queryResponse The query response - * @returns The list of pending action IDs - */ - parsePendingActionIds(queryResponse: string[]): number[] { - try { - if (!queryResponse || queryResponse.length === 0) { - return []; - } - - // Assuming each element in the response is a base64 encoded action ID - return queryResponse.map((item) => { - const buffer = Buffer.from(item, "base64"); - return parseInt(buffer.toString("hex"), 16); - }); - } catch (error) { - throw new Error(`Error parsing pending action IDs: ${error}`); - } - } - - /** - * Parses the outcome of a query to get the multisig contract's board members - * @param queryResponse The query response - * @returns The list of board member addresses - */ - parseBoardMembers(queryResponse: string[]): Address[] { - if (!queryResponse || queryResponse.length === 0) { - return []; - } - - return queryResponse.map((item) => { - const buffer = Buffer.from(item, "base64"); - return new Address(buffer); - }); - } - - /** - * Parses the outcome of a query to get the multisig contract's quorum - * @param queryResponse The query response - * @returns The quorum value + * Parses the outcome of a multisig action proposal + * @param transactionOnNetwork The completed transaction + * @returns In case of scDeploy returns address else undefined */ - parseQuorum(queryResponse: string[]): number { - if (!queryResponse || queryResponse.length === 0) { - throw new Err("No return data available"); - } + parsePerformAction(transactionOnNetwork: TransactionOnNetwork): Address | undefined { + const result = this.parser.parseExecute({ transactionOnNetwork }); - const buffer = Buffer.from(queryResponse[0], "base64"); - return parseInt(buffer.toString("hex"), 16); + return result.values[0]; } } From 53c3069963c2e050d572b96593326a388fca6595 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 6 May 2025 11:23:18 +0300 Subject: [PATCH 12/14] Rename function --- src/multisig/multisigController.ts | 23 ++++++++++++--------- src/multisig/multisigTransactionsFactory.ts | 8 +++---- src/multisig/resources.ts | 4 ++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index f94cae5e..61053f87 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -617,12 +617,15 @@ export class MultisigController extends SmartContractController { /** * Creates a transaction for proposing to deploy a smart contract from source */ - async createTransactionForProposeSCDeployFromSource( + async createTransactionForProposeContractDeployFromSource( sender: IAccount, nonce: bigint, - options: resources.ProposeSCDeployFromSourceInput & BaseControllerInput, + options: resources.ProposeContractDeployFromSourceInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeSCDeployFromSource(sender.address, options); + const transaction = this.multisigFactory.createTransactionForProposeContractDeployFromSource( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -634,9 +637,9 @@ export class MultisigController extends SmartContractController { } /** - * Awaits the completion of a propose SC deploy from source action + * Awaits the completion of a propose Contract deploy from source action */ - async awaitCompletedProposeSCDeployFromSource(txHash: string): Promise { + async awaitCompletedProposeContractDeployFromSource(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); return this.multisigParser.parseProposeAction(transaction); } @@ -644,12 +647,12 @@ export class MultisigController extends SmartContractController { /** * Creates a transaction for proposing to upgrade a smart contract from source */ - async createTransactionForProposeSCUpgradeFromSource( + async createTransactionForProposeContractUpgradeFromSource( sender: IAccount, nonce: bigint, - options: resources.ProposeSCUpgradeFromSourceInput & BaseControllerInput, + options: resources.ProposeContractUpgradeFromSourceInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeSCUpgradeFromSource( + const transaction = this.multisigFactory.createTransactionForProposeContractUpgradeFromSource( sender.address, options, ); @@ -664,9 +667,9 @@ export class MultisigController extends SmartContractController { } /** - * Awaits the completion of a propose SC upgrade from source action + * Awaits the completion of a propose Contract upgrade from source action */ - async awaitCompletedProposeSCUpgradeFromSource(txHash: string): Promise { + async awaitCompletedProposeContractUpgradeFromSource(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); return this.multisigParser.parseProposeAction(transaction); } diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index e7595d4c..1ecd4e2e 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -265,9 +265,9 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor /** * Proposes deploying a smart contract from source */ - createTransactionForProposeSCDeployFromSource( + createTransactionForProposeContractDeployFromSource( sender: Address, - options: resources.ProposeSCDeployFromSourceInput, + options: resources.ProposeContractDeployFromSourceInput, ): Transaction { const dataParts = [ "proposeSCDeployFromSource", @@ -289,9 +289,9 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor /** * Proposes upgrading a smart contract from source */ - createTransactionForProposeSCUpgradeFromSource( + createTransactionForProposeContractUpgradeFromSource( sender: Address, - options: resources.ProposeSCUpgradeFromSourceInput, + options: resources.ProposeContractUpgradeFromSourceInput, ): Transaction { const dataParts = [ "proposeSCUpgradeFromSource", diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index c1e3aa9d..3008133f 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -70,14 +70,14 @@ export type ProposeAsyncCallInput = MultisigContractInput & { abi?: Abi; }; -export type ProposeSCDeployFromSourceInput = MultisigContractInput & { +export type ProposeContractDeployFromSourceInput = MultisigContractInput & { amount: bigint; source: Address; codeMetadata: CodeMetadata; arguments: string[]; }; -export type ProposeSCUpgradeFromSourceInput = MultisigContractInput & { +export type ProposeContractUpgradeFromSourceInput = MultisigContractInput & { scAddress: Address; amount: bigint; source: Address; From 805f71dc7014af3af2afc8462112474273d78651 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 6 May 2025 11:38:24 +0300 Subject: [PATCH 13/14] Comments update --- src/multisig/multisigController.spec.ts | 4 ++-- src/multisig/resources.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts index b61c4e2a..501678a9 100644 --- a/src/multisig/multisigController.spec.ts +++ b/src/multisig/multisigController.spec.ts @@ -221,7 +221,7 @@ describe("test multisig controller query methods", () => { "userRole", new SmartContractQueryResponse({ function: "userRole", - returnDataParts: [Buffer.from("01", "hex")], // 1 = BOARD_MEMBER, for example + returnDataParts: [Buffer.from("01", "hex")], // 1 = PROPOSER, for example returnCode: "ok", returnMessage: "ok", }), @@ -232,7 +232,7 @@ describe("test multisig controller query methods", () => { userAddress: mockBoardMemberAddress, }); - assert.equal(result, "Proposer"); // 1 could be board member role + assert.equal(result, "Proposer"); // 1 could be proposer member role }); it("getAllBoardMembers returns all board members as address array", async function () { diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 3008133f..1b06a9aa 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -198,7 +198,7 @@ export class SendTransferExecuteEsdt extends MultisigAction { this.type = MultisigActionEnum.SendTransferExecuteEsdt; this.receiver = data.to; this.tokens = data.tokens.map( - (token: { token_identifier: any; nonce: any; amount: any }) => + (token: { token_identifier: string; nonce: bigint; amount: bigint }) => new TokenTransfer({ token: new Token({ identifier: token.token_identifier, nonce: token.nonce }), amount: token.amount, From c17402c8be55aa1255c56ffc5ad39afc980ca6f1 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 6 May 2025 11:44:21 +0300 Subject: [PATCH 14/14] Fix build --- src/multisig/multisigTransactionsFactory.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index 73827a6b..8b20a99b 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -290,7 +290,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", ); - const transaction = factory.createTransactionForProposeSCDeployFromSource(senderAddress, { + const transaction = factory.createTransactionForProposeContractDeployFromSource(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, amount: amount, @@ -318,7 +318,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", ); - const transaction = factory.createTransactionForProposeSCUpgradeFromSource(senderAddress, { + const transaction = factory.createTransactionForProposeContractUpgradeFromSource(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, scAddress: multisigContractAddress,