From 59fe5397314bf5059d374b8a988f281666020f71 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 14 Apr 2025 12:24:15 +0300 Subject: [PATCH 01/54] Add multisig transaction factory --- .../multisigTransactionsFactory.spec.ts | 530 ++++ src/multisig/multisigTransactionsFactory.ts | 510 ++++ src/multisig/resources.ts | 433 +++ .../smartContractTransactionsFactory.ts | 6 +- src/testdata/multisig-full.abi.json | 2533 ++++++++--------- 5 files changed, 2730 insertions(+), 1282 deletions(-) create mode 100644 src/multisig/multisigTransactionsFactory.spec.ts create mode 100644 src/multisig/multisigTransactionsFactory.ts create mode 100644 src/multisig/resources.ts diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts new file mode 100644 index 000000000..97bc2900a --- /dev/null +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -0,0 +1,530 @@ +import { assert } from "chai"; +import { Abi, Code } from "../abi"; +import { CodeMetadata, Token, TokenTransfer } from "../core"; +import { Address } from "../core/address"; +import { Transaction } from "../core/transaction"; +import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; +import { loadAbiRegistry, loadContractCode } from "../testutils"; +import { MultisigTransactionsFactory } from "./multisigTransactionsFactory"; + +describe("test multisig transactions factory", function () { + const config = new TransactionsFactoryConfig({ + chainID: "D", + }); + + let bytecode: Code; + let abi: Abi; + let adderAbi: Abi; + let factory: MultisigTransactionsFactory; + before(async function () { + bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + adderAbi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + factory = new MultisigTransactionsFactory({ + config: config, + abi: abi, + }); + }); + + 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 board = [boardMemberAddress, proposerAddress]; + const amount = 1000000000000000000n; // 1 EGLD + + const transaction = factory.createTransactionForMultisigDeploy(senderAddress, { + bytecode: bytecode.valueOf(), + gasLimit: 5000000n, + quorum: 2, + board, + amount, + }); + const res = Buffer.from(transaction.data).toString().split("@"); + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"); + assert.equal(transaction.value, amount); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + Buffer.from(transaction.data), + Buffer.from( + `${bytecode}@0500@0504@02@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba`, + ), + ); + }); + + it("should create transaction for propose add board member", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const boardMemberAddress = Address.newFromBech32( + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + ); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForProposeAddBoardMember(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + boardMemberAddress: boardMemberAddress, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeAddBoardMember@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); + }); + + it("should create transaction for propose add proposer", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const proposerAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForProposeAddProposer(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + proposerAddress: proposerAddress, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeAddProposer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); + }); + + it("should create transaction for propose remove user", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const userAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForProposeRemoveUser(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + userAddress: userAddress, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeRemoveUser@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); + }); + + it("should create transaction for propose change quorum", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForProposeChangeQuorum(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + newQuorum: 3, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual(transaction.data.toString(), "proposeChangeQuorum@03"); + }); + + it("should create transaction for propose transfer execute", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const destinationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms", + ); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq6kurkz43xq8t35kx9p8rvyz5kpxe9g7qd8ssefqjw8", + ); + const amount = 1000000000000000000n; // 1 EGLD + const transaction = factory.createTransactionForProposeTransferExecute(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + egldAmount: amount, + to: destinationContract, + functionName: "add", + functionArguments: [7], + abi: adderAbi, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeTransferExecute@0000000000000000050078d29632acb15998003f615d0a51261353d8041d3e13@0de0b6b3a7640000@0100000000004c4b40@616464@07", + ); + }); + + it("should create transaction for propose transfer execute ESDT", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const destinationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgqfxlljcaalgl2qfcnxcsftheju0ts36kvl3ts3qkewe", + ); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const token = new Token({ + identifier: "ALICE-5627f1", + }); + const tokenTransfer = new TokenTransfer({ token: token, amount: 10n }); + + const transaction = factory.createTransactionForProposeTransferExecuteEsdt(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + to: destinationContract, + tokens: [tokenTransfer], + functionName: "distribute", + functionArguments: [], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeTransferExecuteEsdt@0000000000000000050049bff963bdfa3ea02713362095df32e3d708eaccfc57@0000000c414c4943452d3536323766310000000000000000000000010a@0100000000004c4b40@3634363937333734373236393632373537343635", + ); + }); + + it("should create transaction for propose async call", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const destinationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms", + ); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq6kurkz43xq8t35kx9p8rvyz5kpxe9g7qd8ssefqjw8", + ); + const transaction = factory.createTransactionForProposeAsyncCall(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + nativeTransferAmount: 0n, + to: destinationContract, + functionName: "add", + functionArguments: [7], + tokenTransfers: [], + abi: adderAbi, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.equal( + transaction.data.toString(), + "proposeAsyncCall@0000000000000000050078d29632acb15998003f615d0a51261353d8041d3e13@@4c4b40@616464@07", + ); + }); + + it("should create transaction for deposit the expected amount of egld", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + + const transaction = factory.createTransactionForDeposit(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + egldAmount: 1n, + tokenTransfers: [], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.value, 1n); + assert.deepEqual(transaction.data.toString(), "deposit"); + }); + + it("should create transaction for deposit esdt token", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const token = new Token({ + identifier: "ALICE-5627f1", + }); + const tokenTransfer = new TokenTransfer({ token: token, amount: 100n }); + + const transaction = factory.createTransactionForDeposit(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + egldAmount: 0n, + tokenTransfers: [tokenTransfer], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.value, 0n); + assert.deepEqual(transaction.data.toString(), "ESDTTransfer@414c4943452d353632376631@64@6465706f736974"); + }); + + it("should create transaction for propose SC deploy from source", function () { + const amount = BigInt(50000000000000000); // 0.05 EGLD + const metadata = new CodeMetadata(true, true, false); + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const sourceContract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqsuxsgykwm6r3s5apct2g5a2rcpe7kw0ed8ssf6h9f6"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", + ); + + const transaction = factory.createTransactionForProposeSCDeployFromSource(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + amount: amount, + source: sourceContract, + codeMetadata: metadata, + arguments: ["7"], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeSCDeployFromSource@b1a2bc2ec50000@00000000000000000500870d0412cede871853a1c2d48a7543c073eb39f969e1@0500@7", + ); + }); + + it("should create transaction for propose SC upgrade from source", function () { + const amount = BigInt(50000000000000000); // 0.05 EGLD + const metadata = new CodeMetadata(true, true, false); + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const sourceContract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqd273cw3hjndqzcpts4dvq0ncy8nx8rkgzeusnefvaq"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", + ); + + const transaction = factory.createTransactionForProposeSCUpgradeFromSource(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + scAddress: multisigContractAddress, + amount: amount, + source: sourceContract, + codeMetadata: metadata, + arguments: [], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeSCUpgradeFromSource@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@b1a2bc2ec50000@000000000000000005006abd1c3a3794da01602b855ac03e7821e6638ec81679@0500", + ); + }); + + it("should create transaction for sign action", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForSignAction(senderAddress, { + multisigContract: multisigContractAddress, + actionId: 42, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual(transaction.data.toString(), "sign@2a"); + }); + + it("should create transaction for sign batch", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForSignBatch(senderAddress, { + multisigContract: multisigContractAddress, + groupId: 5, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual(transaction.data.toString(), "signBatch@05"); + }); + + it("should create transaction for sign and perform", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForSignAndPerform(senderAddress, { + multisigContract: multisigContractAddress, + actionId: 42, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual(transaction.data.toString(), "signAndPerform@2a"); + }); + + it("should create transaction for unsign", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForUnsign(senderAddress, { + multisigContract: multisigContractAddress, + actionId: 42, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.deepEqual(transaction.data.toString(), "unsign@2a"); + }); + + it("should create transaction for unsign for outdated board members", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForUnsignForOutdatedBoardMembers(senderAddress, { + multisigContract: multisigContractAddress, + actionId: 42, + outdatedBoardMembers: [1, 3, 5], + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.deepEqual(transaction.data.toString(), "unsignForOutdatedBoardMembers@2a@01@03@05"); + }); + + it("should create transaction for perform action", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForPerformAction(senderAddress, { + multisigContract: multisigContractAddress, + actionId: 42, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.deepEqual(transaction.data.toString(), "performAction@2a"); + }); + + it("should create transaction for perform batch", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForPerformBatch(senderAddress, { + multisigContract: multisigContractAddress, + groupId: 5, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.deepEqual(transaction.data.toString(), "performBatch@05"); + }); + + it("should create transaction for discard action", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForDiscardAction(senderAddress, { + multisigContract: multisigContractAddress, + actionId: 322, + gasLimit: 5000000n, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.isAbove(transaction.data.length, 0); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual(transaction.data.toString(), "discardAction@0142"); + }); + + it("should create transaction for discard batch", function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + ); + const transaction = factory.createTransactionForDiscardBatch(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + actionIds: [24, 25], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual(transaction.data.toString(), "discardBatch@18@19"); + }); +}); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts new file mode 100644 index 000000000..aa5ba209a --- /dev/null +++ b/src/multisig/multisigTransactionsFactory.ts @@ -0,0 +1,510 @@ +import { + AddressValue, + ArgSerializer, + BigUIntValue, + ContractFunction, + EndpointDefinition, + EndpointModifiers, + NativeSerializer, + OptionType, + OptionValue, + TokenIdentifierValue, + U32Value, + U64Type, + U64Value, + VariadicValue, +} from "../abi"; +import { TokenComputer, TransactionsFactoryConfig } from "../core"; +import { Address } from "../core/address"; +import { Transaction } from "../core/transaction"; +import { TransactionBuilder } from "../core/transactionBuilder"; +import { SmartContractTransactionsFactory } from "../smartContracts"; +import * as resources from "./resources"; + +interface IAbi { + constructorDefinition: EndpointDefinition; + upgradeConstructorDefinition?: EndpointDefinition; + + getEndpoint(name: string | ContractFunction): EndpointDefinition; +} +/** + * Use this class to create multisig related transactions like creating a new multisig contract, + * proposing actions, signing actions, and performing actions. + */ +export class MultisigTransactionsFactory extends SmartContractTransactionsFactory { + private readonly argSerializer: ArgSerializer; + + constructor(options: { config: TransactionsFactoryConfig; abi?: IAbi }) { + super(options); + this.argSerializer = new ArgSerializer(); + } + + /** + * Creates a transaction to deploy a new multisig contract + */ + createTransactionForMultisigDeploy(sender: Address, options: resources.DeployMultisigContractInput): Transaction { + const nativeTransferAmount = options.amount ?? 0n; + const boardAddresses: AddressValue[] = options.board.map((addr) => new AddressValue(addr)); + const args = [new U32Value(options.quorum), VariadicValue.fromItems(...boardAddresses)]; + + return this.createTransactionForDeploy(sender, { + bytecode: options.bytecode, + gasLimit: options.gasLimit, + nativeTransferAmount, + isUpgradeable: options.isUpgradeable, + isReadable: options.isReadable, + isPayable: options.isPayable, + isPayableBySmartContract: options.isPayableBySmartContract, + arguments: args, + }); + } + + /** + * Proposes adding a new board member + */ + createTransactionForProposeAddBoardMember( + sender: Address, + options: resources.ProposeAddBoardMemberInput, + ): Transaction { + const dataParts = [ + "proposeAddBoardMember", + this.argSerializer.valuesToStrings([new AddressValue(options.boardMemberAddress)])[0], + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes adding a new proposer + */ + createTransactionForProposeAddProposer(sender: Address, options: resources.ProposeAddProposerInput): Transaction { + const dataParts = [ + "proposeAddProposer", + this.argSerializer.valuesToStrings([new AddressValue(options.proposerAddress)])[0], + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes removing a user (board member or proposer) + */ + createTransactionForProposeRemoveUser(sender: Address, options: resources.ProposeRemoveUserInput): Transaction { + const dataParts = [ + "proposeRemoveUser", + this.argSerializer.valuesToStrings([new AddressValue(options.userAddress)])[0], + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes changing the quorum (minimum signatures required) + */ + createTransactionForProposeChangeQuorum(sender: Address, options: resources.ProposeChangeQuorumInput): Transaction { + const dataParts = [ + "proposeChangeQuorum", + this.argSerializer.valuesToStrings([new U32Value(options.newQuorum)])[0], + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes a transaction that will transfer EGLD and/or execute a function + */ + createTransactionForProposeTransferExecute( + sender: Address, + options: resources.ProposeTransferExecuteInput, + ): Transaction { + const gasOption = new U64Value(options.gasLimit); + const input = resources.ProposeTransferExecutInput.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], + ...input.functionCall, + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes a transaction that will transfer EGLD and/or execute a function + */ + createTransactionForDeposit(sender: Address, options: resources.DepositExecuteInput): Transaction { + return this.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "deposit", + gasLimit: options.gasLimit ?? 0n, + arguments: [], + nativeTransferAmount: options.egldAmount, + tokenTransfers: options.tokenTransfers, + }); + } + + /** + * Proposes a transaction that will transfer ESDT tokens and/or execute a function + */ + createTransactionForProposeTransferExecuteEsdt( + sender: Address, + options: resources.ProposeTransferExecuteEsdtInput, + ): Transaction { + const input = resources.ProposeTransferExecutInput.newFromTransferExecuteInput({ + multisig: options.multisigContract, + to: options.to, + tokenTransfers: [], + functionName: options.functionName, + arguments: options.functionArguments, + abi: options.abi, + }); + const tokenComputer = new TokenComputer(); + const argsTyped = []; + for (const token of options.tokens) { + const identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier); + argsTyped.push({ + token_identifier: new TokenIdentifierValue( + tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier), + ), + token_nonce: new U64Value(token.token.nonce), + amount: new BigUIntValue(token.amount), + }); + } + const dataParts = [ + "proposeTransferExecuteEsdt", + ...this.argSerializer.valuesToStrings( + NativeSerializer.nativeToTypedValues( + [options.to, argsTyped, options.gasLimit, VariadicValue.fromItems(...input.functionCall)], + this.abi?.getEndpoint("proposeTransferExecuteEsdt") ?? + new EndpointDefinition("proposeTransferExecuteEsdt", [], [], new EndpointModifiers("", [])), + ), + ), + ]; + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes an async call to another contract + */ + createTransactionForProposeAsyncCall(sender: Address, options: resources.ProposeAsyncCallInput): Transaction { + const input = resources.ProposeTransferExecutInput.newFromTransferExecuteInput({ + multisig: options.multisigContract, + to: options.to, + tokenTransfers: options.tokenTransfers, + functionName: options.functionName, + arguments: options.functionArguments, + abi: options.abi, + }); + 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], + ...input.functionCall, + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: receiver, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: false, + amount: 0n, + }).build(); + } + + /** + * Proposes deploying a smart contract from source + */ + createTransactionForProposeSCDeployFromSource( + sender: Address, + options: resources.ProposeSCDeployFromSourceInput, + ): Transaction { + const dataParts = [ + "proposeSCDeployFromSource", + this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0], + this.argSerializer.valuesToStrings([new AddressValue(options.source)])[0], + options.codeMetadata.toString(), + ...options.arguments, + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Proposes upgrading a smart contract from source + */ + createTransactionForProposeSCUpgradeFromSource( + sender: Address, + options: resources.ProposeSCUpgradeFromSourceInput, + ): 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], + options.codeMetadata.toString(), + ...options.arguments, + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Signs an action (by a board member) + */ + createTransactionForSignAction(sender: Address, options: resources.ActionInput): Transaction { + const dataParts = ["sign", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Signs all actions in a batch + */ + createTransactionForSignBatch(sender: Address, options: resources.GroupInput): Transaction { + const dataParts = ["signBatch", this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Signs and performs an action in one transaction + */ + createTransactionForSignAndPerform(sender: Address, options: resources.ActionInput): Transaction { + const dataParts = ["signAndPerform", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Signs and performs all actions in a batch + */ + createTransactionForSignBatchAndPerform(sender: Address, options: resources.GroupInput): Transaction { + const dataParts = [ + "signBatchAndPerform", + this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0], + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Withdraws signature from an action + */ + createTransactionForUnsign(sender: Address, options: resources.ActionInput): Transaction { + const dataParts = ["unsign", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Withdraws signatures from all actions in a batch + */ + createTransactionForUnsignBatch(sender: Address, options: resources.GroupInput): Transaction { + const dataParts = ["unsignBatch", this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Removes signatures from outdated board members + */ + createTransactionForUnsignForOutdatedBoardMembers( + sender: Address, + options: resources.UnsignForOutdatedBoardMembersInput, + ): Transaction { + const outdatedMembersArgs = options.outdatedBoardMembers.map( + (id) => this.argSerializer.valuesToStrings([new U32Value(id)])[0], + ); + + const dataParts = [ + "unsignForOutdatedBoardMembers", + this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0], + ...outdatedMembersArgs, + ]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Performs an action that has reached quorum + */ + createTransactionForPerformAction(sender: Address, options: resources.ActionInput): Transaction { + const dataParts = ["performAction", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Performs all actions in a batch that have reached quorum + */ + createTransactionForPerformBatch(sender: Address, options: resources.GroupInput): Transaction { + const dataParts = ["performBatch", this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Discards an action that is no longer needed + */ + createTransactionForDiscardAction(sender: Address, options: resources.ActionInput): Transaction { + const dataParts = ["discardAction", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } + + /** + * Discards all actions in the provided list + */ + createTransactionForDiscardBatch(sender: Address, options: resources.DiscardBatchInput): Transaction { + const actionIdsArgs = options.actionIds.map((id) => this.argSerializer.valuesToStrings([new U32Value(id)])[0]); + + const dataParts = ["discardBatch", ...actionIdsArgs]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: options.multisigContract, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: true, + }).build(); + } +} diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts new file mode 100644 index 000000000..04c49a6d2 --- /dev/null +++ b/src/multisig/resources.ts @@ -0,0 +1,433 @@ +import { Abi, BytesValue } from "../abi"; +import { Token, TokenTransfer, TransactionsFactoryConfig } 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; + board: Address[]; + amount?: bigint; + bytecode: Uint8Array; + isUpgradeable?: boolean; + isReadable?: boolean; + isPayable?: boolean; + isPayableBySmartContract?: boolean; + gasLimit: bigint; +}; + +export type UpgradeMultisigContractInput = { + bytecode: Uint8Array; + isUpgradeable?: boolean; + isReadable?: boolean; + isPayable?: boolean; + isPayableBySmartContract?: boolean; + multisigContract: Address; + gasLimit: bigint; +}; +export type MultisigContractInput = { + multisigContract: Address; + gasLimit: bigint; +}; + +export type ProposeAddBoardMemberInput = MultisigContractInput & { + boardMemberAddress: Address; +}; + +export type ProposeAddProposerInput = MultisigContractInput & { + proposerAddress: Address; +}; + +export type ProposeRemoveUserInput = MultisigContractInput & { + userAddress: Address; +}; + +export type ProposeChangeQuorumInput = MultisigContractInput & { + newQuorum: number; +}; + +export type ProposeTransferExecuteInput = MultisigContractInput & { + to: Address; + egldAmount: bigint; + gasLimit?: bigint; + functionName: string; + functionArguments: any[]; + abi?: Abi; +}; + +export type DepositExecuteInput = MultisigContractInput & { + egldAmount: bigint; + gasLimit?: bigint; + tokenTransfers: TokenTransfer[]; +}; + +export class ProposeTransferExecuteEsdtInput { + multisigContract: Address; + to: Address; + tokens: any[]; + gasLimit: 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 { + multisigContract: Address; + to: Address; + nativeTransferAmount: bigint; + tokenTransfers: TokenTransfer[]; + functionName: string; + 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; + source: Address; + codeMetadata: CodeMetadata; + arguments: string[]; +}; + +export type ProposeSCUpgradeFromSourceInput = MultisigContractInput & { + scAddress: Address; + amount: bigint; + source: Address; + codeMetadata: CodeMetadata; + arguments: string[]; +}; + +export type ActionInput = MultisigContractInput & { + actionId: number; +}; + +export type GroupInput = MultisigContractInput & { + groupId: number; +}; + +export type UnsignForOutdatedBoardMembersInput = ActionInput & { + outdatedBoardMembers: number[]; +}; + +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; + optionalGasLimit?: number | null; + functionName: Uint8Array; + arguments: Uint8Array[]; +}; + +export type EsdtTokenPayment = { + token_identifier: any; + token_nonce: any; + amount: any; +}; + +export type EsdtTransferExecuteData = { + to: Address; + tokens: EsdtTokenPayment[]; + opt_gas_limit?: number | null; + endpoint_name: Uint8Array; + arguments: Uint8Array[]; +}; diff --git a/src/smartContracts/smartContractTransactionsFactory.ts b/src/smartContracts/smartContractTransactionsFactory.ts index 521a8f600..d66b0ecd6 100644 --- a/src/smartContracts/smartContractTransactionsFactory.ts +++ b/src/smartContracts/smartContractTransactionsFactory.ts @@ -30,8 +30,8 @@ interface IAbi { * Use this class to create transactions to deploy, call or upgrade a smart contract. */ export class SmartContractTransactionsFactory { - private readonly config: IConfig; - private readonly abi?: IAbi; + protected readonly config: IConfig; + protected readonly abi?: IAbi; private readonly tokenComputer: TokenComputer; private readonly dataArgsBuilder: TokenTransfersDataBuilder; private readonly contractDeployAddress: Address; @@ -195,7 +195,7 @@ export class SmartContractTransactionsFactory { }).build(); } - private argsToDataParts(args: any[], endpoint?: EndpointDefinition): string[] { + protected argsToDataParts(args: any[], endpoint?: EndpointDefinition): string[] { if (endpoint) { const typedArgs = NativeSerializer.nativeToTypedValues(args, endpoint); return new ArgSerializer().valuesToStrings(typedArgs); diff --git a/src/testdata/multisig-full.abi.json b/src/testdata/multisig-full.abi.json index 401de7cc0..6e9657dce 100644 --- a/src/testdata/multisig-full.abi.json +++ b/src/testdata/multisig-full.abi.json @@ -1,1304 +1,1279 @@ { - "buildInfo": { - "rustc": { - "version": "1.71.0-nightly", - "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", - "commitDate": "2023-05-25", - "channel": "Nightly", - "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" - }, - "contractCrate": { - "name": "multisig", - "version": "1.0.0", - "gitVersion": "v0.45.2.1-reproducible-169-g37d970c" - }, - "framework": { - "name": "multiversx-sc", - "version": "0.47.2" - } - }, - "docs": [ - "Multi-signature smart contract implementation.", - "Acts like a wallet that needs multiple signers for any action performed.", - "See the readme file for more detailed documentation." - ], "name": "Multisig", "constructor": { - "inputs": [ - { - "name": "quorum", - "type": "u32" - }, - { - "name": "board", - "type": "variadic
", - "multi_arg": true - } - ], - "outputs": [] - }, - "endpoints": [ - { - "name": "upgrade", - "mutability": "mutable", - "inputs": [], - "outputs": [] - }, - { - "docs": [ - "Allows the contract to receive funds even if it is marked as unpayable in the protocol." - ], - "name": "deposit", - "mutability": "mutable", - "payableInTokens": [ - "*" - ], - "inputs": [], - "outputs": [] - }, - { - "docs": [ - "Clears storage pertaining to an action that is no longer supposed to be executed.", - "Any signatures that the action received must first be removed, via `unsign`.", - "Otherwise this endpoint would be prone to abuse." - ], - "name": "discardAction", - "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [] - }, - { - "docs": [ - "Discard all the actions with the given IDs" - ], - "name": "discardBatch", - "mutability": "mutable", - "inputs": [ - { - "name": "action_ids", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [] - }, - { - "docs": [ - "Minimum number of signatures needed to perform any action." - ], - "name": "getQuorum", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Denormalized board member count.", - "It is kept in sync with the user list by the contract." - ], - "name": "getNumBoardMembers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "getNumGroups", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Denormalized proposer count.", - "It is kept in sync with the user list by the contract." - ], - "name": "getNumProposers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "getActionGroup", - "mutability": "readonly", - "inputs": [ - { - "name": "group_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "variadic", - "multi_result": true - } - ] - }, - { - "name": "getLastGroupActionId", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "The index of the last proposed action.", - "0 means that no action was ever proposed yet." - ], - "name": "getActionLastIndex", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Initiates board member addition process.", - "Can also be used to promote a proposer to board member." - ], - "name": "proposeAddBoardMember", - "mutability": "mutable", - "inputs": [ - { - "name": "board_member_address", - "type": "Address" - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Initiates proposer addition process..", - "Can also be used to demote a board member to proposer." - ], - "name": "proposeAddProposer", - "mutability": "mutable", - "inputs": [ - { - "name": "proposer_address", - "type": "Address" - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Removes user regardless of whether it is a board member or proposer." - ], - "name": "proposeRemoveUser", - "mutability": "mutable", - "inputs": [ - { - "name": "user_address", - "type": "Address" - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "proposeChangeQuorum", - "mutability": "mutable", - "inputs": [ - { - "name": "new_quorum", - "type": "u32" - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Propose a transaction in which the contract will perform a transfer-execute call.", - "Can send EGLD without calling anything.", - "Can call smart contract endpoints directly.", - "Doesn't really work with builtin functions." - ], - "name": "proposeTransferExecute", - "mutability": "mutable", - "inputs": [ - { - "name": "to", - "type": "Address" - }, - { - "name": "egld_amount", - "type": "BigUint" - }, - { - "name": "opt_gas_limit", - "type": "Option" - }, - { - "name": "function_call", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "proposeTransferExecuteEsdt", - "mutability": "mutable", - "inputs": [ - { - "name": "to", - "type": "Address" - }, - { - "name": "tokens", - "type": "List" - }, - { - "name": "opt_gas_limit", - "type": "Option" - }, - { - "name": "function_call", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Propose a transaction in which the contract will perform an async call call.", - "Can call smart contract endpoints directly.", - "Can use ESDTTransfer/ESDTNFTTransfer/MultiESDTTransfer to send tokens, while also optionally calling endpoints.", - "Works well with builtin functions.", - "Cannot simply send EGLD directly without calling anything." - ], - "name": "proposeAsyncCall", - "mutability": "mutable", - "inputs": [ - { - "name": "to", - "type": "Address" - }, - { - "name": "egld_amount", - "type": "BigUint" - }, - { - "name": "opt_gas_limit", - "type": "Option" - }, - { - "name": "function_call", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "proposeSCDeployFromSource", - "mutability": "mutable", - "inputs": [ - { - "name": "amount", - "type": "BigUint" - }, - { - "name": "source", - "type": "Address" - }, - { - "name": "code_metadata", - "type": "CodeMetadata" - }, - { - "name": "arguments", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "proposeSCUpgradeFromSource", - "mutability": "mutable", - "inputs": [ - { - "name": "sc_address", - "type": "Address" - }, - { - "name": "amount", - "type": "BigUint" - }, - { - "name": "source", - "type": "Address" - }, - { - "name": "code_metadata", - "type": "CodeMetadata" - }, - { - "name": "arguments", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "name": "proposeBatch", - "mutability": "mutable", - "inputs": [ - { - "name": "actions", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "u32" - } - ] - }, - { - "docs": [ - "Used by board members to sign actions." - ], - "name": "sign", - "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [] - }, + "inputs": [ { - "docs": [ - "Sign all the actions in the given batch" - ], - "name": "signBatch", - "mutability": "mutable", - "inputs": [ - { - "name": "group_id", - "type": "u32" - } - ], - "outputs": [] + "name": "quorum", + "type": "u32" }, { - "name": "signAndPerform", - "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "optional
", - "multi_result": true - } - ] - }, - { - "name": "signBatchAndPerform", - "mutability": "mutable", - "inputs": [ - { - "name": "group_id", - "type": "u32" - } - ], - "outputs": [] - }, - { - "docs": [ - "Board members can withdraw their signatures if they no longer desire for the action to be executed.", - "Actions that are left with no valid signatures can be then deleted to free up storage." - ], - "name": "unsign", - "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [] - }, - { - "docs": [ - "Unsign all actions with the given IDs" - ], - "name": "unsignBatch", - "mutability": "mutable", - "inputs": [ - { - "name": "group_id", - "type": "u32" - } - ], - "outputs": [] - }, - { - "docs": [ - "Returns `true` (`1`) if the user has signed the action.", - "Does not check whether or not the user is still a board member and the signature valid." - ], - "name": "signed", - "mutability": "readonly", - "inputs": [ - { - "name": "user", - "type": "Address" - }, - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "bool" - } - ] - }, - { - "name": "unsignForOutdatedBoardMembers", - "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - }, - { - "name": "outdated_board_members", - "type": "variadic", - "multi_arg": true - } - ], - "outputs": [] - }, - { - "docs": [ - "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." - ], - "name": "quorumReached", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "bool" - } - ] - }, - { - "docs": [ - "Proposers and board members use this to launch signed actions." - ], - "name": "performAction", - "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "optional
", - "multi_result": true - } - ] - }, - { - "docs": [ - "Perform all the actions in the given batch" - ], - "name": "performBatch", - "mutability": "mutable", - "inputs": [ - { - "name": "group_id", - "type": "u32" - } - ], - "outputs": [] - }, - { - "name": "dnsRegister", - "onlyOwner": true, - "mutability": "mutable", - "payableInTokens": [ - "EGLD" - ], - "inputs": [ - { - "name": "dns_address", - "type": "Address" - }, - { - "name": "name", - "type": "bytes" - } - ], - "outputs": [] - }, - { - "docs": [ - "Iterates through all actions and retrieves those that are still pending.", - "Serialized full action data:", - "- the action id", - "- the serialized action data", - "- (number of signers followed by) list of signer addresses." - ], - "name": "getPendingActionFullInfo", - "mutability": "readonly", - "inputs": [ - { - "name": "opt_range", - "type": "optional>", - "multi_arg": true - } - ], - "outputs": [ - { - "type": "variadic", - "multi_result": true - } - ], - "labels": [ - "multisig-external-view" - ], - "allow_multiple_var_args": true - }, - { - "docs": [ - "Indicates user rights.", - "`0` = no rights,", - "`1` = can propose, but not sign,", - "`2` = can propose and sign." - ], - "name": "userRole", - "mutability": "readonly", - "inputs": [ - { - "name": "user", - "type": "Address" - } - ], - "outputs": [ - { - "type": "UserRole" - } - ], - "labels": [ - "multisig-external-view" - ] - }, - { - "docs": [ - "Lists all users that can sign actions." - ], - "name": "getAllBoardMembers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic
", - "multi_result": true - } - ], - "labels": [ - "multisig-external-view" - ] - }, - { - "docs": [ - "Lists all proposers that are not board members." - ], - "name": "getAllProposers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic
", - "multi_result": true - } - ], - "labels": [ - "multisig-external-view" - ] - }, - { - "docs": [ - "Serialized action data of an action with index." - ], - "name": "getActionData", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "Action" - } - ], - "labels": [ - "multisig-external-view" - ] - }, - { - "docs": [ - "Gets addresses of all users who signed an action.", - "Does not check if those users are still board members or not,", - "so the result may contain invalid signers." - ], - "name": "getActionSigners", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "List
" - } - ], - "labels": [ - "multisig-external-view" - ] - }, - { - "docs": [ - "Gets addresses of all users who signed an action and are still board members.", - "All these signatures are currently valid." - ], - "name": "getActionSignerCount", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "u32" - } - ], - "labels": [ - "multisig-external-view" - ] - }, - { - "docs": [ - "It is possible for board members to lose their role.", - "They are not automatically removed from all actions when doing so,", - "therefore the contract needs to re-check every time when actions are performed.", - "This function is used to validate the signers before performing an action.", - "It also makes it easy to check before performing an action." - ], - "name": "getActionValidSignerCount", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "u32" - } - ], - "labels": [ - "multisig-external-view" - ] + "name": "board", + "type": "variadic
", + "multi_arg": true } + ], + "outputs": [] + }, + "upgradeConstructor": { + "inputs": [], + "outputs": [] + }, + "endpoints": [ + { + "docs": [ + "Allows the contract to receive funds even if it is marked as unpayable in the protocol." + ], + "name": "deposit", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [], + "outputs": [] + }, + { + "docs": [ + "Clears storage pertaining to an action that is no longer supposed to be executed.", + "Any signatures that the action received must first be removed, via `unsign`.", + "Otherwise this endpoint would be prone to abuse." + ], + "name": "discardAction", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Discard all the actions with the given IDs" + ], + "name": "discardBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "action_ids", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "docs": [ + "Minimum number of signatures needed to perform any action." + ], + "name": "getQuorum", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Denormalized board member count.", + "It is kept in sync with the user list by the contract." + ], + "name": "getNumBoardMembers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "getNumGroups", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Denormalized proposer count.", + "It is kept in sync with the user list by the contract." + ], + "name": "getNumProposers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "getActionGroup", + "mutability": "readonly", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "getLastGroupActionId", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "The index of the last proposed action.", + "0 means that no action was ever proposed yet." + ], + "name": "getActionLastIndex", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Initiates board member addition process.", + "Can also be used to promote a proposer to board member." + ], + "name": "proposeAddBoardMember", + "mutability": "mutable", + "inputs": [ + { + "name": "board_member_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Initiates proposer addition process..", + "Can also be used to demote a board member to proposer." + ], + "name": "proposeAddProposer", + "mutability": "mutable", + "inputs": [ + { + "name": "proposer_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Removes user regardless of whether it is a board member or proposer." + ], + "name": "proposeRemoveUser", + "mutability": "mutable", + "inputs": [ + { + "name": "user_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeChangeQuorum", + "mutability": "mutable", + "inputs": [ + { + "name": "new_quorum", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Propose a transaction in which the contract will perform a transfer-execute call.", + "Can send EGLD without calling anything.", + "Can call smart contract endpoints directly.", + "Doesn't really work with builtin functions." + ], + "name": "proposeTransferExecute", + "mutability": "mutable", + "inputs": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "egld_amount", + "type": "BigUint" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeTransferExecuteEsdt", + "mutability": "mutable", + "inputs": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "tokens", + "type": "List" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Propose a transaction in which the contract will perform an async call call.", + "Can call smart contract endpoints directly.", + "Can use ESDTTransfer/ESDTNFTTransfer/MultiESDTTransfer to send tokens, while also optionally calling endpoints.", + "Works well with builtin functions.", + "Cannot simply send EGLD directly without calling anything." + ], + "name": "proposeAsyncCall", + "mutability": "mutable", + "inputs": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "egld_amount", + "type": "BigUint" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeSCDeployFromSource", + "mutability": "mutable", + "inputs": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "source", + "type": "Address" + }, + { + "name": "code_metadata", + "type": "CodeMetadata" + }, + { + "name": "arguments", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeSCUpgradeFromSource", + "mutability": "mutable", + "inputs": [ + { + "name": "sc_address", + "type": "Address" + }, + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "source", + "type": "Address" + }, + { + "name": "code_metadata", + "type": "CodeMetadata" + }, + { + "name": "arguments", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "actions", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "docs": [ + "Used by board members to sign actions." + ], + "name": "sign", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Sign all the actions in the given batch" + ], + "name": "signBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "signAndPerform", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "optional
", + "multi_result": true + } + ] + }, + { + "name": "signBatchAndPerform", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Board members can withdraw their signatures if they no longer desire for the action to be executed.", + "Actions that are left with no valid signatures can be then deleted to free up storage." + ], + "name": "unsign", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Unsign all actions with the given IDs" + ], + "name": "unsignBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Returns `true` (`1`) if the user has signed the action.", + "Does not check whether or not the user is still a board member and the signature valid." + ], + "name": "signed", + "mutability": "readonly", + "inputs": [ + { + "name": "user", + "type": "Address" + }, + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "unsignForOutdatedBoardMembers", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + }, + { + "name": "outdated_board_members", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "docs": [ + "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." + ], + "name": "quorumReached", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "docs": [ + "Proposers and board members use this to launch signed actions." + ], + "name": "performAction", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "optional
", + "multi_result": true + } + ] + }, + { + "docs": [ + "Perform all the actions in the given batch" + ], + "name": "performBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "dnsRegister", + "onlyOwner": true, + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [ + { + "name": "dns_address", + "type": "Address" + }, + { + "name": "name", + "type": "bytes" + } + ], + "outputs": [] + }, + { + "docs": [ + "Iterates through all actions and retrieves those that are still pending.", + "Serialized full action data:", + "- the action id", + "- the serialized action data", + "- (number of signers followed by) list of signer addresses." + ], + "name": "getPendingActionFullInfo", + "mutability": "readonly", + "inputs": [ + { + "name": "opt_range", + "type": "optional>", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ], + "allow_multiple_var_args": true + }, + { + "docs": [ + "Indicates user rights.", + "`0` = no rights,", + "`1` = can propose, but not sign,", + "`2` = can propose and sign." + ], + "name": "userRole", + "mutability": "readonly", + "inputs": [ + { + "name": "user", + "type": "Address" + } + ], + "outputs": [ + { + "type": "UserRole" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Lists all users that can sign actions." + ], + "name": "getAllBoardMembers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Lists all proposers that are not board members." + ], + "name": "getAllProposers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Serialized action data of an action with index." + ], + "name": "getActionData", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "Action" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Gets addresses of all users who signed an action.", + "Does not check if those users are still board members or not,", + "so the result may contain invalid signers." + ], + "name": "getActionSigners", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "List
" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Gets addresses of all users who signed an action and are still board members.", + "All these signatures are currently valid." + ], + "name": "getActionSignerCount", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "It is possible for board members to lose their role.", + "They are not automatically removed from all actions when doing so,", + "therefore the contract needs to re-check every time when actions are performed.", + "This function is used to validate the signers before performing an action.", + "It also makes it easy to check before performing an action." + ], + "name": "getActionValidSignerCount", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ], + "labels": [ + "multisig-external-view" + ] + } ], "events": [ - { - "identifier": "asyncCallSuccess", - "inputs": [ - { - "name": "results", - "type": "variadic", - "indexed": true - } - ] - }, - { - "identifier": "asyncCallError", - "inputs": [ - { - "name": "err_code", - "type": "u32", - "indexed": true - }, - { - "name": "err_message", - "type": "bytes", - "indexed": true - } - ] - }, - { - "identifier": "startPerformAction", - "inputs": [ - { - "name": "data", - "type": "ActionFullInfo" - } - ] - }, - { - "identifier": "performChangeUser", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "changed_user", - "type": "Address", - "indexed": true - }, - { - "name": "old_role", - "type": "UserRole", - "indexed": true - }, - { - "name": "new_role", - "type": "UserRole", - "indexed": true - } - ] - }, - { - "identifier": "performChangeQuorum", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "new_quorum", - "type": "u32", - "indexed": true - } - ] - }, - { - "identifier": "performAsyncCall", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "to", - "type": "Address", - "indexed": true - }, - { - "name": "egld_value", - "type": "BigUint", - "indexed": true - }, - { - "name": "gas", - "type": "u64", - "indexed": true - }, - { - "name": "endpoint", - "type": "bytes", - "indexed": true - }, - { - "name": "arguments", - "type": "variadic", - "indexed": true - } - ] - }, - { - "identifier": "performTransferExecuteEgld", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "to", - "type": "Address", - "indexed": true - }, - { - "name": "egld_value", - "type": "BigUint", - "indexed": true - }, - { - "name": "gas", - "type": "u64", - "indexed": true - }, - { - "name": "endpoint", - "type": "bytes", - "indexed": true - }, - { - "name": "arguments", - "type": "variadic", - "indexed": true - } - ] - }, - { - "identifier": "performTransferExecuteEsdt", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "to", - "type": "Address", - "indexed": true - }, - { - "name": "tokens", - "type": "List", - "indexed": true - }, - { - "name": "gas", - "type": "u64", - "indexed": true - }, - { - "name": "endpoint", - "type": "bytes", - "indexed": true - }, - { - "name": "arguments", - "type": "variadic", - "indexed": true - } - ] - }, - { - "identifier": "performDeployFromSource", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "egld_value", - "type": "BigUint", - "indexed": true - }, - { - "name": "source_address", - "type": "Address", - "indexed": true - }, - { - "name": "code_metadata", - "type": "CodeMetadata", - "indexed": true - }, - { - "name": "gas", - "type": "u64", - "indexed": true - }, - { - "name": "arguments", - "type": "variadic", - "indexed": true - } - ] - }, - { - "identifier": "performUpgradeFromSource", - "inputs": [ - { - "name": "action_id", - "type": "u32", - "indexed": true - }, - { - "name": "target_address", - "type": "Address", - "indexed": true - }, - { - "name": "egld_value", - "type": "BigUint", - "indexed": true - }, - { - "name": "source_address", - "type": "Address", - "indexed": true - }, - { - "name": "code_metadata", - "type": "CodeMetadata", - "indexed": true - }, - { - "name": "gas", - "type": "u64", - "indexed": true - }, - { - "name": "arguments", - "type": "variadic", - "indexed": true - } - ] - } + { + "identifier": "asyncCallSuccess", + "inputs": [ + { + "name": "results", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "asyncCallError", + "inputs": [ + { + "name": "err_code", + "type": "u32", + "indexed": true + }, + { + "name": "err_message", + "type": "bytes", + "indexed": true + } + ] + }, + { + "identifier": "startPerformAction", + "inputs": [ + { + "name": "data", + "type": "ActionFullInfo" + } + ] + }, + { + "identifier": "performChangeUser", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "changed_user", + "type": "Address", + "indexed": true + }, + { + "name": "old_role", + "type": "UserRole", + "indexed": true + }, + { + "name": "new_role", + "type": "UserRole", + "indexed": true + } + ] + }, + { + "identifier": "performChangeQuorum", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "new_quorum", + "type": "u32", + "indexed": true + } + ] + }, + { + "identifier": "performAsyncCall", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performTransferExecuteEgld", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performTransferExecuteEsdt", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "tokens", + "type": "List", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performDeployFromSource", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "source_address", + "type": "Address", + "indexed": true + }, + { + "name": "code_metadata", + "type": "CodeMetadata", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performUpgradeFromSource", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "target_address", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "source_address", + "type": "Address", + "indexed": true + }, + { + "name": "code_metadata", + "type": "CodeMetadata", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + } ], "esdtAttributes": [], "hasCallback": true, "types": { - "Action": { - "type": "enum", - "variants": [ - { - "name": "Nothing", - "discriminant": 0 - }, - { - "name": "AddBoardMember", - "discriminant": 1, - "fields": [ - { - "name": "0", - "type": "Address" - } - ] - }, - { - "name": "AddProposer", - "discriminant": 2, - "fields": [ - { - "name": "0", - "type": "Address" - } - ] - }, - { - "name": "RemoveUser", - "discriminant": 3, - "fields": [ - { - "name": "0", - "type": "Address" - } - ] - }, - { - "name": "ChangeQuorum", - "discriminant": 4, - "fields": [ - { - "name": "0", - "type": "u32" - } - ] - }, - { - "name": "SendTransferExecuteEgld", - "discriminant": 5, - "fields": [ - { - "name": "0", - "type": "CallActionData" - } - ] - }, - { - "name": "SendTransferExecuteEsdt", - "discriminant": 6, - "fields": [ - { - "name": "0", - "type": "EsdtTransferExecuteData" - } - ] - }, - { - "name": "SendAsyncCall", - "discriminant": 7, - "fields": [ - { - "name": "0", - "type": "CallActionData" - } - ] - }, - { - "name": "SCDeployFromSource", - "discriminant": 8, - "fields": [ - { - "name": "amount", - "type": "BigUint" - }, - { - "name": "source", - "type": "Address" - }, - { - "name": "code_metadata", - "type": "CodeMetadata" - }, - { - "name": "arguments", - "type": "List" - } - ] - }, - { - "name": "SCUpgradeFromSource", - "discriminant": 9, - "fields": [ - { - "name": "sc_address", - "type": "Address" - }, - { - "name": "amount", - "type": "BigUint" - }, - { - "name": "source", - "type": "Address" - }, - { - "name": "code_metadata", - "type": "CodeMetadata" - }, - { - "name": "arguments", - "type": "List" - } - ] - } + "Action": { + "type": "enum", + "variants": [ + { + "name": "Nothing", + "discriminant": 0 + }, + { + "name": "AddBoardMember", + "discriminant": 1, + "fields": [ + { + "name": "0", + "type": "Address" + } ] - }, - "ActionFullInfo": { - "type": "struct", - "docs": [ - "Not used internally, just to retrieve results via endpoint." - ], + }, + { + "name": "AddProposer", + "discriminant": 2, "fields": [ - { - "name": "action_id", - "type": "u32" - }, - { - "name": "group_id", - "type": "u32" - }, - { - "name": "action_data", - "type": "Action" - }, - { - "name": "signers", - "type": "List
" - } + { + "name": "0", + "type": "Address" + } ] - }, - "ActionStatus": { - "type": "enum", - "variants": [ - { - "name": "Available", - "discriminant": 0 - }, - { - "name": "Aborted", - "discriminant": 1 - } + }, + { + "name": "RemoveUser", + "discriminant": 3, + "fields": [ + { + "name": "0", + "type": "Address" + } ] - }, - "CallActionData": { - "type": "struct", + }, + { + "name": "ChangeQuorum", + "discriminant": 4, "fields": [ - { - "name": "to", - "type": "Address" - }, - { - "name": "egld_amount", - "type": "BigUint" - }, - { - "name": "opt_gas_limit", - "type": "Option" - }, - { - "name": "endpoint_name", - "type": "bytes" - }, - { - "name": "arguments", - "type": "List" - } + { + "name": "0", + "type": "u32" + } ] - }, - "EsdtTokenPayment": { - "type": "struct", + }, + { + "name": "SendTransferExecuteEgld", + "discriminant": 5, "fields": [ - { - "name": "token_identifier", - "type": "TokenIdentifier" - }, - { - "name": "token_nonce", - "type": "u64" - }, - { - "name": "amount", - "type": "BigUint" - } + { + "name": "0", + "type": "CallActionData" + } ] - }, - "EsdtTransferExecuteData": { - "type": "struct", + }, + { + "name": "SendTransferExecuteEsdt", + "discriminant": 6, "fields": [ - { - "name": "to", - "type": "Address" - }, - { - "name": "tokens", - "type": "List" - }, - { - "name": "opt_gas_limit", - "type": "Option" - }, - { - "name": "endpoint_name", - "type": "bytes" - }, - { - "name": "arguments", - "type": "List" - } + { + "name": "0", + "type": "EsdtTransferExecuteData" + } ] - }, - "UserRole": { - "type": "enum", - "variants": [ - { - "name": "None", - "discriminant": 0 - }, - { - "name": "Proposer", - "discriminant": 1 - }, - { - "name": "BoardMember", - "discriminant": 2 - } + }, + { + "name": "SendAsyncCall", + "discriminant": 7, + "fields": [ + { + "name": "0", + "type": "CallActionData" + } ] - } + }, + { + "name": "SCDeployFromSource", + "discriminant": 8, + "fields": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "source", + "type": "Address" + }, + { + "name": "code_metadata", + "type": "CodeMetadata" + }, + { + "name": "arguments", + "type": "List" + } + ] + }, + { + "name": "SCUpgradeFromSource", + "discriminant": 9, + "fields": [ + { + "name": "sc_address", + "type": "Address" + }, + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "source", + "type": "Address" + }, + { + "name": "code_metadata", + "type": "CodeMetadata" + }, + { + "name": "arguments", + "type": "List" + } + ] + } + ] + }, + "ActionFullInfo": { + "type": "struct", + "docs": [ + "Not used internally, just to retrieve results via endpoint." + ], + "fields": [ + { + "name": "action_id", + "type": "u32" + }, + { + "name": "group_id", + "type": "u32" + }, + { + "name": "action_data", + "type": "Action" + }, + { + "name": "signers", + "type": "List
" + } + ] + }, + "ActionStatus": { + "type": "enum", + "variants": [ + { + "name": "Available", + "discriminant": 0 + }, + { + "name": "Aborted", + "discriminant": 1 + } + ] + }, + "CallActionData": { + "type": "struct", + "fields": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "egld_amount", + "type": "BigUint" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "endpoint_name", + "type": "bytes" + }, + { + "name": "arguments", + "type": "List" + } + ] + }, + "EsdtTokenPayment": { + "type": "struct", + "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "token_nonce", + "type": "u64" + }, + { + "name": "amount", + "type": "BigUint" + } + ] + }, + "EsdtTransferExecuteData": { + "type": "struct", + "fields": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "tokens", + "type": "List" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "endpoint_name", + "type": "bytes" + }, + { + "name": "arguments", + "type": "List" + } + ] + }, + "UserRole": { + "type": "enum", + "variants": [ + { + "name": "None", + "discriminant": 0 + }, + { + "name": "Proposer", + "discriminant": 1 + }, + { + "name": "BoardMember", + "discriminant": 2 + } + ] + } } -} + } \ No newline at end of file From d9fd92ae910a69b541ed6085fd7335ac03a1aa47 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 10:45:11 +0300 Subject: [PATCH 02/54] Remove unused variables --- src/multisig/multisigTransactionsFactory.spec.ts | 1 - src/multisig/multisigTransactionsFactory.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index 97bc2900a..e177b1795 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -45,7 +45,6 @@ describe("test multisig transactions factory", function () { board, amount, }); - const res = Buffer.from(transaction.data).toString().split("@"); assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index aa5ba209a..9bd5ac0c2 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -204,7 +204,6 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor const tokenComputer = new TokenComputer(); const argsTyped = []; for (const token of options.tokens) { - const identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier); argsTyped.push({ token_identifier: new TokenIdentifierValue( tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier), From 33906160cacb6407526a289a73a00f30ce8dceed Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 11:06:35 +0300 Subject: [PATCH 03/54] remove empty comment --- src/multisig/resources.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 04c49a6d2..8da1888ed 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -209,9 +209,6 @@ export class ProposeAsyncCallInput { functionArguments: any[]; gasLimit: bigint; abi?: Abi; - /** - * - */ constructor(options: { multisigContract: Address; to: Address; From 0f558478075873022889151792c52fc1a3fabd46 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 11:33:16 +0300 Subject: [PATCH 04/54] Remove unused resources --- src/multisig/resources.ts | 165 +------------------------------------- 1 file changed, 1 insertion(+), 164 deletions(-) diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 8da1888ed..29dac7b5b 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -1,5 +1,5 @@ import { Abi, BytesValue } from "../abi"; -import { Token, TokenTransfer, TransactionsFactoryConfig } from "../core"; +import { TokenTransfer, TransactionsFactoryConfig } from "../core"; import { Address } from "../core/address"; import { CodeMetadata } from "../core/codeMetadata"; import { ARGUMENTS_SEPARATOR } from "../core/constants"; @@ -18,15 +18,6 @@ export type DeployMultisigContractInput = { gasLimit: bigint; }; -export type UpgradeMultisigContractInput = { - bytecode: Uint8Array; - isUpgradeable?: boolean; - isReadable?: boolean; - isPayable?: boolean; - isPayableBySmartContract?: boolean; - multisigContract: Address; - gasLimit: bigint; -}; export type MultisigContractInput = { multisigContract: Address; gasLimit: bigint; @@ -261,152 +252,6 @@ 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; @@ -420,11 +265,3 @@ export type EsdtTokenPayment = { token_nonce: any; amount: any; }; - -export type EsdtTransferExecuteData = { - to: Address; - tokens: EsdtTokenPayment[]; - opt_gas_limit?: number | null; - endpoint_name: Uint8Array; - arguments: Uint8Array[]; -}; From 14bf65e02b56d26ccc8e4952b30b620b4b3a4682 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 12:30:12 +0300 Subject: [PATCH 05/54] 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 000000000..45bace699 --- /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 000000000..79c9855bf --- /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 29dac7b5b..45a09d59b 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 cd370bbcc..59efe28f7 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 06/54] 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 79c9855bf..03def8539 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 000000000..28c100d25 --- /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 93569cd8cdb6a9a8f14052a4b1474ad1fc046974 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 15:26:47 +0300 Subject: [PATCH 07/54] 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 b9e1ef27e..cb9f585e8 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 000000000..5185e2f02 --- /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 e177b1795..05f4c1e29 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 9bd5ac0c2..e564b0e55 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 000000000..c6fcb78be --- /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 29dac7b5b..e0b67be5e 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -1,10 +1,7 @@ -import { Abi, BytesValue } from "../abi"; -import { TokenTransfer, TransactionsFactoryConfig } from "../core"; +import { Abi } from "../abi"; +import { 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 dbd39448f4cdc874dd72a44f9698531d8b4b0572 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 15:29:46 +0300 Subject: [PATCH 08/54] 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 12a2eb439..cf06e4119 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 1e5d2d8b4..ddc442e9d 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 925f5d7992daab5b301f50c78cd45dc5227abcbd Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Apr 2025 15:26:47 +0300 Subject: [PATCH 09/54] 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 b9e1ef27e..cb9f585e8 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 000000000..5185e2f02 --- /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 e177b1795..05f4c1e29 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 9bd5ac0c2..e564b0e55 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 000000000..c6fcb78be --- /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 45a09d59b..546fde6cc 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 10/54] 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 12a2eb439..cf06e4119 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 1e5d2d8b4..ddc442e9d 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 11/54] 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 03def8539..79c9855bf 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 12/54] 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 cf06e4119..877e8a78f 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 ddc442e9d..90ff967ee 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 067c7e2af13971e9f0518612904c39a9cb84d443 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 16 Apr 2025 14:51:12 +0300 Subject: [PATCH 13/54] Add methods to export multisig controller and factopry on entrypoints --- package-lock.json | 4 ++-- package.json | 2 +- src/entrypoints/entrypoints.ts | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 877e8a78f..8cf5ad1f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.1", + "version": "14.1.0-beta.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.1", + "version": "14.1.0-beta.2", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 90ff967ee..ae97c2b82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.1", + "version": "14.1.0-beta.2", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", diff --git a/src/entrypoints/entrypoints.ts b/src/entrypoints/entrypoints.ts index 9431e71b0..8c12d80a7 100644 --- a/src/entrypoints/entrypoints.ts +++ b/src/entrypoints/entrypoints.ts @@ -12,6 +12,8 @@ import { TransactionWatcher, } from "../core"; import { DelegationController, DelegationTransactionsFactory } from "../delegation"; +import { MultisigTransactionsFactory } from "../multisig"; +import { MultisigController } from "../multisig/multisigController"; import { ApiNetworkProvider, ProxyNetworkProvider } from "../networkProviders"; import { INetworkProvider } from "../networkProviders/interface"; import { SmartContractTransactionsFactory } from "../smartContracts"; @@ -180,6 +182,14 @@ export class NetworkEntrypoint { config: new TransactionsFactoryConfig({ chainID: this.chainId }), }); } + + createMultisigController(): MultisigController { + return new MultisigController({ chainID: this.chainId, networkProvider: this.networkProvider }); + } + + createMultisigTransactionsFactory(): MultisigTransactionsFactory { + return new MultisigTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }) }); + } } export class TestnetEntrypoint extends NetworkEntrypoint { From 02828b57ba30925a5847e0199f082842c4ca746e Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 16 Apr 2025 15:22:26 +0300 Subject: [PATCH 14/54] Fix deploy transaction --- src/multisig/multisigTransactionsFactory.spec.ts | 4 +--- src/multisig/multisigTransactionsFactory.ts | 2 -- src/multisig/resources.ts | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index 05f4c1e29..3309e623f 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -34,19 +34,17 @@ describe("test multisig transactions factory", function () { const boardMemberTwo = Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); const board = [boardMemberOne, boardMemberTwo]; - const amount = 1000000000000000000n; // 1 EGLD const transaction = factory.createTransactionForMultisigDeploy(senderAddress, { bytecode: bytecode.valueOf(), gasLimit: 5000000n, quorum: 2, board, - amount, }); assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"); - assert.equal(transaction.value, amount); + assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); assert.deepEqual( Buffer.from(transaction.data), diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index e564b0e55..54385e044 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -43,14 +43,12 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor * Creates a transaction to deploy a new multisig contract */ createTransactionForMultisigDeploy(sender: Address, options: resources.DeployMultisigContractInput): Transaction { - const nativeTransferAmount = options.amount ?? 0n; const boardAddresses: AddressValue[] = options.board.map((addr) => new AddressValue(addr)); const args = [new U32Value(options.quorum), VariadicValue.fromItems(...boardAddresses)]; return this.createTransactionForDeploy(sender, { bytecode: options.bytecode, gasLimit: options.gasLimit, - nativeTransferAmount, isUpgradeable: options.isUpgradeable, isReadable: options.isReadable, isPayable: options.isPayable, diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index e0b67be5e..d71f46736 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -6,7 +6,6 @@ import { CodeMetadata } from "../core/codeMetadata"; export type DeployMultisigContractInput = { quorum: number; board: Address[]; - amount?: bigint; bytecode: Uint8Array; isUpgradeable?: boolean; isReadable?: boolean; From 2bcc31c66c480067756bb60e182209ed10516337 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 17 Apr 2025 16:04:27 +0300 Subject: [PATCH 15/54] 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 45bace699..28dea289f 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 79c9855bf..cfd060ba2 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 4fae9572a..d4ce70cfe 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 be6ca32e48ce33a2f7b7c867b3eb548891344022 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 17 Apr 2025 16:04:27 +0300 Subject: [PATCH 16/54] 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 45bace699..28dea289f 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 79c9855bf..cfd060ba2 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 4fae9572a..d4ce70cfe 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 b3f7d9bf7f0d8de9ef43644f4c367c33c5d56098 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 17 Apr 2025 16:06:33 +0300 Subject: [PATCH 17/54] 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 8cf5ad1f1..5d8df766b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.2", + "version": "14.1.0-beta.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.2", + "version": "14.1.0-beta.3", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index ae97c2b82..8d8756e08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.2", + "version": "14.1.0-beta.3", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From 79c8bb44c9fe57fa8a4ee9bb1df592810c1b80ad Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 17 Apr 2025 16:07:13 +0300 Subject: [PATCH 18/54] remove console.log --- src/multisig/multisigController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index cfd060ba2..86be2d4d2 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -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()), From 9243f444b2b7a60e50037625b70014c9c4f0b1aa Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 22 Apr 2025 12:20:59 +0300 Subject: [PATCH 19/54] Remove IAbi and use concret implementation --- src/abi/smartContract.ts | 20 ++++++++----------- src/multisig/index.ts | 2 +- src/multisig/multisigTransactionsFactory.ts | 11 +++------- ...=> proposeTransferExecuteContractInput.ts} | 0 .../smartContractTransactionsFactory.ts | 13 +++--------- 5 files changed, 15 insertions(+), 31 deletions(-) rename src/multisig/{proposeTransferExecuteContract.ts => proposeTransferExecuteContractInput.ts} (100%) diff --git a/src/abi/smartContract.ts b/src/abi/smartContract.ts index 7282430be..5c991eef7 100644 --- a/src/abi/smartContract.ts +++ b/src/abi/smartContract.ts @@ -19,14 +19,7 @@ import { } from "./interface"; import { NativeSerializer } from "./nativeSerializer"; import { Query } from "./query"; -import { EndpointDefinition, TypedValue } from "./typesystem"; - -interface IAbi { - constructorDefinition: EndpointDefinition; - - getEndpoints(): EndpointDefinition[]; - getEndpoint(name: string | ContractFunction): EndpointDefinition; -} +import { Abi, EndpointDefinition, TypedValue } from "./typesystem"; /** * * @deprecated component. Use "SmartContractTransactionsFactory" or "SmartContractController", instead. @@ -35,7 +28,7 @@ interface IAbi { */ export class SmartContract implements ISmartContract { private address: Address = Address.empty(); - private abi?: IAbi; + private abi?: Abi; /** * This object contains a function for each endpoint defined by the contract. @@ -55,7 +48,7 @@ export class SmartContract implements ISmartContract { /** * Create a SmartContract object by providing its address on the Network. */ - constructor(options: { address?: Address; abi?: IAbi } = {}) { + constructor(options: { address?: Address; abi?: Abi } = {}) { this.address = options.address || Address.empty(); this.abi = options.abi; @@ -105,13 +98,16 @@ export class SmartContract implements ISmartContract { return this.address; } - private getAbi(): IAbi { + private getAbi(): Abi { guardValueIsSet("abi", this.abi); return this.abi!; } getEndpoint(name: string | ContractFunction): EndpointDefinition { - return this.getAbi().getEndpoint(name); + if (typeof name === "string") { + return this.getAbi().getEndpoint(name); + } + return this.getAbi().getEndpoint(name.name); } /** diff --git a/src/multisig/index.ts b/src/multisig/index.ts index 5185e2f02..969a85053 100644 --- a/src/multisig/index.ts +++ b/src/multisig/index.ts @@ -1,3 +1,3 @@ export * from "./multisigTransactionsFactory"; -export * from "./proposeTransferExecuteContract"; +export * from "./ProposeTransferExecuteContractInput"; export * from "./resources"; diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 54385e044..81d736349 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -1,4 +1,5 @@ import { + Abi, AddressValue, ArgSerializer, BigUIntValue, @@ -18,15 +19,9 @@ import { Address } from "../core/address"; import { Transaction } from "../core/transaction"; import { TransactionBuilder } from "../core/transactionBuilder"; import { SmartContractTransactionsFactory } from "../smartContracts"; -import { ProposeTransferExecuteContractInput } from "./proposeTransferExecuteContract"; +import { ProposeTransferExecuteContractInput } from "./ProposeTransferExecuteContractInput"; import * as resources from "./resources"; -interface IAbi { - constructorDefinition: EndpointDefinition; - upgradeConstructorDefinition?: EndpointDefinition; - - getEndpoint(name: string): EndpointDefinition; -} /** * Use this class to create multisig related transactions like creating a new multisig contract, * proposing actions, signing actions, and performing actions. @@ -34,7 +29,7 @@ interface IAbi { export class MultisigTransactionsFactory extends SmartContractTransactionsFactory { private readonly argSerializer: ArgSerializer; - constructor(options: { config: TransactionsFactoryConfig; abi?: IAbi }) { + constructor(options: { config: TransactionsFactoryConfig; abi?: Abi }) { super(options); this.argSerializer = new ArgSerializer(); } diff --git a/src/multisig/proposeTransferExecuteContract.ts b/src/multisig/proposeTransferExecuteContractInput.ts similarity index 100% rename from src/multisig/proposeTransferExecuteContract.ts rename to src/multisig/proposeTransferExecuteContractInput.ts diff --git a/src/smartContracts/smartContractTransactionsFactory.ts b/src/smartContracts/smartContractTransactionsFactory.ts index d66b0ecd6..5fc334322 100644 --- a/src/smartContracts/smartContractTransactionsFactory.ts +++ b/src/smartContracts/smartContractTransactionsFactory.ts @@ -1,4 +1,4 @@ -import { ArgSerializer, ContractFunction, EndpointDefinition, isTyped, NativeSerializer } from "../abi"; +import { Abi, ArgSerializer, EndpointDefinition, isTyped, NativeSerializer } from "../abi"; import { Address, CodeMetadata } from "../core"; import { CONTRACT_DEPLOY_ADDRESS_HEX, VM_TYPE_WASM_VM } from "../core/constants"; import { Err } from "../core/errors"; @@ -19,24 +19,17 @@ interface IConfig { gasLimitChangeOwnerAddress: bigint; } -interface IAbi { - constructorDefinition: EndpointDefinition; - upgradeConstructorDefinition?: EndpointDefinition; - - getEndpoint(name: string | ContractFunction): EndpointDefinition; -} - /** * Use this class to create transactions to deploy, call or upgrade a smart contract. */ export class SmartContractTransactionsFactory { protected readonly config: IConfig; - protected readonly abi?: IAbi; + protected readonly abi?: Abi; private readonly tokenComputer: TokenComputer; private readonly dataArgsBuilder: TokenTransfersDataBuilder; private readonly contractDeployAddress: Address; - constructor(options: { config: IConfig; abi?: IAbi }) { + constructor(options: { config: IConfig; abi?: Abi }) { this.config = options.config; this.abi = options.abi; this.tokenComputer = new TokenComputer(); From 2e3f9361a7748729bec238e0627e906057da1a6f Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 22 Apr 2025 12:28:28 +0300 Subject: [PATCH 20/54] Refector ProposeTransferExecuteContractInput --- .../proposeTransferExecuteContractInput.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/multisig/proposeTransferExecuteContractInput.ts b/src/multisig/proposeTransferExecuteContractInput.ts index c6fcb78be..6c89b6cbc 100644 --- a/src/multisig/proposeTransferExecuteContractInput.ts +++ b/src/multisig/proposeTransferExecuteContractInput.ts @@ -1,5 +1,5 @@ import { Abi, BytesValue } from "../abi"; -import { Address, TokenTransfer, TransactionsFactoryConfig } from "../core"; +import { Address, TokenTransfer, Transaction, TransactionsFactoryConfig } from "../core"; import { ARGUMENTS_SEPARATOR } from "../core/constants"; import { utf8ToHex } from "../core/utils.codec"; import { SmartContractTransactionsFactory } from "../smartContracts"; @@ -36,14 +36,9 @@ export class ProposeTransferExecuteContractInput { 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]; + + const functionCall = ProposeTransferExecuteContractInput.getFunctionCall(transaction); + return new ProposeTransferExecuteContractInput({ multisigContract: options.multisig, to: options.to, @@ -72,14 +67,9 @@ export class ProposeTransferExecuteContractInput { 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]; + + const functionCall = ProposeTransferExecuteContractInput.getFunctionCall(transaction); + return new ProposeTransferExecuteContractInput({ multisigContract: options.multisig, to: options.to, @@ -87,4 +77,12 @@ export class ProposeTransferExecuteContractInput { gasLimit: options.optGasLimit, }); } + + private static getFunctionCall(transaction: Transaction) { + const functionCallParts = Buffer.from(transaction.data).toString().split(ARGUMENTS_SEPARATOR); + const functionName = functionCallParts[0]; + const functionArguments = functionCallParts.slice(1).map((part) => part.valueOf()); + const functionCall = [new BytesValue(Buffer.from(utf8ToHex(functionName))), ...functionArguments]; + return functionCall; + } } From 18e42ece4c79a1bfe55aea0d58076968445cd33c Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 22 Apr 2025 12:30:58 +0300 Subject: [PATCH 21/54] Extract mapTokenPayment --- src/multisig/multisigTransactionsFactory.ts | 28 +++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 81d736349..55326e75f 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -194,17 +194,8 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor arguments: options.functionArguments, abi: options.abi, }); - const tokenComputer = new TokenComputer(); - const argsTyped = []; - for (const token of options.tokens) { - argsTyped.push({ - token_identifier: new TokenIdentifierValue( - tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier), - ), - token_nonce: new U64Value(token.token.nonce), - amount: new BigUIntValue(token.amount), - }); - } + + const argsTyped = this.mapTokenPayment(options); const dataParts = [ "proposeTransferExecuteEsdt", ...this.argSerializer.valuesToStrings( @@ -225,6 +216,21 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor }).build(); } + private mapTokenPayment(options: resources.ProposeTransferExecuteEsdtInput) { + const tokenComputer = new TokenComputer(); + const argsTyped = []; + for (const token of options.tokens) { + argsTyped.push({ + token_identifier: new TokenIdentifierValue( + tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier), + ), + token_nonce: new U64Value(token.token.nonce), + amount: new BigUIntValue(token.amount), + }); + } + return argsTyped; + } + /** * Proposes an async call to another contract */ From 8a4ea680cd976498decb5435dfa6491cade8a8ee Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 22 Apr 2025 16:08:39 +0300 Subject: [PATCH 22/54] Fix build --- src/multisig/index.ts | 2 +- src/multisig/multisigTransactionsFactory.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/multisig/index.ts b/src/multisig/index.ts index 969a85053..7af23fc96 100644 --- a/src/multisig/index.ts +++ b/src/multisig/index.ts @@ -1,3 +1,3 @@ export * from "./multisigTransactionsFactory"; -export * from "./ProposeTransferExecuteContractInput"; +export * from "./proposeTransferExecuteContractInput"; export * from "./resources"; diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 55326e75f..65d837abe 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -7,7 +7,7 @@ import { EndpointModifiers, NativeSerializer, OptionType, - OptionValue, + OptionValue,ßß TokenIdentifierValue, U32Value, U64Type, @@ -19,7 +19,7 @@ import { Address } from "../core/address"; import { Transaction } from "../core/transaction"; import { TransactionBuilder } from "../core/transactionBuilder"; import { SmartContractTransactionsFactory } from "../smartContracts"; -import { ProposeTransferExecuteContractInput } from "./ProposeTransferExecuteContractInput"; +import { ProposeTransferExecuteContractInput } from "./proposeTransferExecuteContractInput"; import * as resources from "./resources"; /** From 6c07b4cfbe0c2dd565dd55b5d7949d8d41d48571 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 22 Apr 2025 16:09:26 +0300 Subject: [PATCH 23/54] Remove extra characters --- src/multisig/multisigTransactionsFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 65d837abe..9c22464d7 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -7,7 +7,7 @@ import { EndpointModifiers, NativeSerializer, OptionType, - OptionValue,ßß + OptionValue, TokenIdentifierValue, U32Value, U64Type, From 7b02b905ddba225a3c63dbbf436e17b51b1cf949 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 24 Apr 2025 08:06:07 +0300 Subject: [PATCH 24/54] 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 28dea289f..ad448acf7 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 cfd060ba2..ce837740f 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 d4ce70cfe..c1e3aa9d9 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 0e2b460e99a312651ac8274ed8685efba8870d9d Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 24 Apr 2025 08:06:07 +0300 Subject: [PATCH 25/54] Update getAllBoardMembers mapping --- src/multisig/multisigController.spec.ts | 16 ++++++++-------- src/multisig/multisigController.ts | 11 +++++------ src/multisig/resources.ts | 20 ++++++++++++++------ 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts index 28dea289f..ad448acf7 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 86be2d4d2..ce837740f 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.", @@ -286,7 +286,7 @@ export class MultisigController extends SmartContractController { arguments: [options.actionId], }); - return Number(Buffer.from(response[0]).toString()); + return Number(response[0].toString()); } /** @@ -826,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 d4ce70cfe..c1e3aa9d9 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 5fdef5a484831016472d0747deafd74c610fb6c8 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 24 Apr 2025 08:07:47 +0300 Subject: [PATCH 26/54] 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 5d8df766b..a27bea7eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.3", + "version": "14.1.0-beta.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.3", + "version": "14.1.0-beta.4", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 8d8756e08..1b873fc06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.3", + "version": "14.1.0-beta.4", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From e74c7ed3c20d90a36866a552b4fb6a2786b6dfe3 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 29 Apr 2025 11:34:34 +0300 Subject: [PATCH 27/54] Update map tokenPayments --- .../multisigTransactionsFactory.spec.ts | 18 ---------------- src/multisig/multisigTransactionsFactory.ts | 21 ++++++++----------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index 3309e623f..73827a6bf 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -70,7 +70,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), @@ -165,7 +164,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), @@ -199,7 +197,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), @@ -229,7 +226,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.equal( transaction.data.toString(), @@ -254,7 +250,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.equal(transaction.value, 1n); assert.deepEqual(transaction.data.toString(), "deposit"); @@ -281,7 +276,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.equal(transaction.value, 0n); assert.deepEqual(transaction.data.toString(), "ESDTTransfer@414c4943452d353632376631@64@6465706f736974"); @@ -308,7 +302,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), @@ -338,7 +331,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), @@ -361,8 +353,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); - assert.equal(transaction.chainID, config.chainID); assert.deepEqual(transaction.data.toString(), "sign@2a"); }); @@ -382,7 +372,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual(transaction.data.toString(), "signBatch@05"); }); @@ -402,8 +391,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); - assert.equal(transaction.chainID, config.chainID); assert.deepEqual(transaction.data.toString(), "signAndPerform@2a"); }); @@ -423,7 +410,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.deepEqual(transaction.data.toString(), "unsign@2a"); }); @@ -442,7 +428,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.deepEqual(transaction.data.toString(), "unsignForOutdatedBoardMembers@2a@01@03@05"); }); @@ -460,7 +445,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.deepEqual(transaction.data.toString(), "performAction@2a"); }); @@ -478,7 +462,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.deepEqual(transaction.data.toString(), "performBatch@05"); }); @@ -497,7 +480,6 @@ describe("test multisig transactions factory", function () { assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); - assert.isAbove(transaction.data.length, 0); assert.equal(transaction.chainID, config.chainID); assert.deepEqual(transaction.data.toString(), "discardAction@0142"); }); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 9c22464d7..d0870a12b 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -8,7 +8,6 @@ import { NativeSerializer, OptionType, OptionValue, - TokenIdentifierValue, U32Value, U64Type, U64Value, @@ -195,12 +194,12 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor abi: options.abi, }); - const argsTyped = this.mapTokenPayment(options); + const tokenPayments: resources.EsdtTokenPayment[] = this.mapTokenPayments(options); const dataParts = [ "proposeTransferExecuteEsdt", ...this.argSerializer.valuesToStrings( NativeSerializer.nativeToTypedValues( - [options.to, argsTyped, options.gasLimit, VariadicValue.fromItems(...input.functionCall)], + [options.to, tokenPayments, options.gasLimit, VariadicValue.fromItems(...input.functionCall)], this.abi?.getEndpoint("proposeTransferExecuteEsdt") ?? new EndpointDefinition("proposeTransferExecuteEsdt", [], [], new EndpointModifiers("", [])), ), @@ -216,19 +215,17 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor }).build(); } - private mapTokenPayment(options: resources.ProposeTransferExecuteEsdtInput) { + private mapTokenPayments(options: resources.ProposeTransferExecuteEsdtInput): resources.EsdtTokenPayment[] { const tokenComputer = new TokenComputer(); - const argsTyped = []; + const tokens = []; for (const token of options.tokens) { - argsTyped.push({ - token_identifier: new TokenIdentifierValue( - tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier), - ), - token_nonce: new U64Value(token.token.nonce), - amount: new BigUIntValue(token.amount), + tokens.push({ + token_identifier: tokenComputer.extractIdentifierFromExtendedIdentifier(token.token.identifier), + token_nonce: token.token.nonce, + amount: token.amount, }); } - return argsTyped; + return tokens; } /** From 721eb9d80c89b22515a2b512b1406fc08d049af4 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 29 Apr 2025 15:21:17 +0300 Subject: [PATCH 28/54] Increase sleep for integration tests --- .github/workflows/test-localnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-localnet.yml b/.github/workflows/test-localnet.yml index d324b54d1..61cdae4c0 100644 --- a/.github/workflows/test-localnet.yml +++ b/.github/workflows/test-localnet.yml @@ -42,7 +42,7 @@ jobs: mkdir -p ~/localnet && cd ~/localnet mxpy localnet setup --configfile=${GITHUB_WORKSPACE}/localnet.toml nohup mxpy localnet start --configfile=${GITHUB_WORKSPACE}/localnet.toml > localnet.log 2>&1 & echo $! > localnet.pid - sleep 10 # Allow time for the testnet to fully start + sleep 120 # Allow time for the testnet to fully start # Step 6: Install Node.js and dependencies - name: Set up Node.js environment From 6ff152e02427f8193d6cad5d291d06168efac0f5 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 5 May 2025 11:41:42 +0300 Subject: [PATCH 29/54] 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 ce837740f..6a6debffb 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 28c100d25..ec06c426f 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 27246b570ad86b49066b12fe8a3b34bac69cde56 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 5 May 2025 14:29:27 +0300 Subject: [PATCH 30/54] Pass abi when create multisig via entrypoint --- src/entrypoints/entrypoints.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/entrypoints/entrypoints.ts b/src/entrypoints/entrypoints.ts index 8c12d80a7..4c5765c40 100644 --- a/src/entrypoints/entrypoints.ts +++ b/src/entrypoints/entrypoints.ts @@ -183,12 +183,15 @@ export class NetworkEntrypoint { }); } - createMultisigController(): MultisigController { - return new MultisigController({ chainID: this.chainId, networkProvider: this.networkProvider }); + createMultisigController(abi?: Abi): MultisigController { + return new MultisigController({ chainID: this.chainId, networkProvider: this.networkProvider, abi: abi }); } - createMultisigTransactionsFactory(): MultisigTransactionsFactory { - return new MultisigTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }) }); + createMultisigTransactionsFactory(abi?: Abi): MultisigTransactionsFactory { + return new MultisigTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: this.chainId }), + abi: abi, + }); } } From dcffb10c20233d5440faf8445cfbf2f2ee4ef0d0 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 5 May 2025 14:33:51 +0300 Subject: [PATCH 31/54] 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 a27bea7eb..03972f656 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.4", + "version": "14.1.0-beta.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.4", + "version": "14.1.0-beta.5", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 1b873fc06..da8a7a8c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.4", + "version": "14.1.0-beta.5", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From 71ab609748f5daba4b5fcbdeb4ec9e13633beb8e Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 5 May 2025 16:00:40 +0300 Subject: [PATCH 32/54] 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 ad448acf7..b61c4e2a1 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 6a6debffb..5054bc7c3 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 d0870a12b..e7595d4cb 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 ec06c426f..640bbe266 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 33/54] 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 5054bc7c3..f94cae5e4 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 640bbe266..d595bf4f3 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 34/54] 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 f94cae5e4..61053f878 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 e7595d4cb..1ecd4e2ef 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 c1e3aa9d9..3008133fa 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 35/54] 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 b61c4e2a1..501678a93 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 3008133fa..1b06a9aa4 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 36/54] 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 73827a6bf..8b20a99b4 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, From fb8248a6ccc10e55170dc5ff47d0e9b98412feeb Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 6 May 2025 13:37:05 +0300 Subject: [PATCH 37/54] Fix build --- src/entrypoints/entrypoints.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/entrypoints/entrypoints.ts b/src/entrypoints/entrypoints.ts index 4c5765c40..eb4cd1232 100644 --- a/src/entrypoints/entrypoints.ts +++ b/src/entrypoints/entrypoints.ts @@ -183,11 +183,11 @@ export class NetworkEntrypoint { }); } - createMultisigController(abi?: Abi): MultisigController { + createMultisigController(abi: Abi): MultisigController { return new MultisigController({ chainID: this.chainId, networkProvider: this.networkProvider, abi: abi }); } - createMultisigTransactionsFactory(abi?: Abi): MultisigTransactionsFactory { + createMultisigTransactionsFactory(abi: Abi): MultisigTransactionsFactory { return new MultisigTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }), abi: abi, From 3fdb8a8ab0b8361a2603cfcb1c2cf7130072153a Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 6 May 2025 15:24:37 +0300 Subject: [PATCH 38/54] Clean-up multisig controller and factory --- src/multisig/multisigController.ts | 82 ++-- .../multisigTransactionsFactory.spec.ts | 4 +- src/multisig/multisigTransactionsFactory.ts | 394 +++++++----------- .../smartContractTransactionsFactory.ts | 4 +- 4 files changed, 197 insertions(+), 287 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 61053f878..6a38aa658 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -2,6 +2,7 @@ import BigNumber from "bignumber.js"; import { Abi } from "../abi"; import { Address, + BaseController, BaseControllerInput, IAccount, Transaction, @@ -14,31 +15,36 @@ import { MultisigTransactionsFactory } from "./multisigTransactionsFactory"; import { MultisigTransactionsOutcomeParser } from "./multisigTransactionsOutcomeParser"; import * as resources from "./resources"; -export class MultisigController extends SmartContractController { +export class MultisigController extends BaseController { private transactionAwaiter: TransactionWatcher; private multisigFactory: MultisigTransactionsFactory; private multisigParser: MultisigTransactionsOutcomeParser; + private smartContractController: SmartContractController; constructor(options: { chainID: string; networkProvider: INetworkProvider; abi: Abi }) { - super(options); - this.abi = options.abi; + super(); this.transactionAwaiter = new TransactionWatcher(options.networkProvider); this.multisigFactory = new MultisigTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), abi: options.abi, }); this.multisigParser = new MultisigTransactionsOutcomeParser({ abi: options.abi }); + this.smartContractController = new SmartContractController({ + chainID: options.chainID, + networkProvider: options.networkProvider, + abi: options.abi, + }); } /** * Creates a transaction for deploying a new multisig contract */ - async createTransactionForMultisigDeploy( + async createTransactionForDeploy( sender: IAccount, nonce: bigint, options: resources.DeployMultisigContractInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForMultisigDeploy(sender.address, options); + const transaction = this.multisigFactory.createTransactionForDeploy(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -53,90 +59,89 @@ export class MultisigController extends SmartContractController { * Gets quorum for specific multisig */ async getQuorum(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getQuorum", arguments: [], }); - return Number(response[0].toString()); + return Number(value.toString()); } /** * Gets number of board members for specific multisig */ async getNumBoardMembers(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getNumBoardMembers", arguments: [], }); - - return Number(response[0].toString()); + return Number(value.toString()); } /** * Gets number of groups for specific multisig */ async getNumGroups(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getNumGroups", arguments: [], }); - return Number(response[0].toString()); + return Number(value.toString()); } /** * Gets number of proposers for specific multisig */ async getNumProposers(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getNumProposers", arguments: [], }); - return Number(response[0].toString()); + return Number(value.toString()); } /** * Gets action group for specific multisig */ async getActionGroup(options: { mutisigAddress: string; groupId: number }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getActionGroup", arguments: [options.groupId], }); - return response[0].map((n: BigNumber) => Number(n.toString())); + return value.map((n: BigNumber) => Number(n.toString())); } /** * Gets last group action id specific multisig */ async getLastGroupActionId(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getLastGroupActionId", arguments: [], }); - return Number(response[0].toString()); + return Number(value.toString()); } /** * Gets last action index specific multisig */ async getActionLastIndex(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getActionLastIndex", arguments: [], }); - return Number(response[0].toString()); + return Number(value.toString()); } /** @@ -148,52 +153,52 @@ export class MultisigController extends SmartContractController { userAddress: string; actionId: number; }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "signed", arguments: [Address.newFromBech32(options.userAddress), options.actionId], }); - return response[0]; + return value; } /** * Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`. */ async quorumReached(options: { mutisigAddress: string; actionId: number }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "quorumReached", arguments: [options.actionId], }); - return response[0]; + return value; } /** * Lists all users that can sign actions. */ async getAllBoardMembers(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getAllBoardMembers", arguments: [], }); - return response[0].map((address: Address) => address?.toBech32()); + return value.map((address: Address) => address?.toBech32()); } /** * Lists all proposers that are not board members. */ async getAllProposers(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getAllProposers", arguments: [], }); - return response[0].map((address: Address) => address?.toBech32()); + return value.map((address: Address) => address?.toBech32()); } /** * "Indicates user rights.", @@ -202,12 +207,12 @@ export class MultisigController extends SmartContractController { * `2` = can propose and sign. */ async getUserRole(options: { mutisigAddress: string; userAddress: string }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "userRole", arguments: [Address.newFromBech32(options.userAddress)], }); - const userRole = response[0].valueOf().name as keyof typeof resources.UserRoleEnum; + const userRole = value.valueOf().name as keyof typeof resources.UserRoleEnum; return resources.UserRoleEnum[userRole]; } @@ -215,12 +220,12 @@ export class MultisigController extends SmartContractController { * Serialized action data of an action with index. */ async getActionData(options: { mutisigAddress: string; actionId: number }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getActionData", arguments: [options.actionId], }); - const result = this.mapResponseToAction(response[0].valueOf()); + const result = this.mapResponseToAction(value.valueOf()); return result; } @@ -228,14 +233,13 @@ export class MultisigController extends SmartContractController { * Gets all pending actions. */ async getPendingActionFullInfo(options: { mutisigAddress: string }): Promise { - const response = await this.query({ + const [actions] = await this.smartContractController.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]; result.push({ @@ -253,7 +257,7 @@ export class MultisigController extends SmartContractController { * 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({ + const response = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getActionSigners", arguments: [options.actionId], @@ -267,13 +271,13 @@ export class MultisigController extends SmartContractController { * All these signatures are currently valid. */ async getActionSignerCount(options: { mutisigAddress: string; actionId: number }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getActionSignerCount", arguments: [options.actionId], }); - return response[0]; + return value; } /** @@ -281,13 +285,13 @@ export class MultisigController extends SmartContractController { * All these signatures are currently valid. */ async getActionValidSignerCount(options: { mutisigAddress: string; actionId: number }): Promise { - const response = await this.query({ + const [value] = await this.smartContractController.query({ contract: Address.newFromBech32(options.mutisigAddress), function: "getActionValidSignerCount", arguments: [options.actionId], }); - return Number(response[0].toString()); + return Number(value.toString()); } /** diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index 8b20a99b4..525da2114 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -35,7 +35,7 @@ describe("test multisig transactions factory", function () { const board = [boardMemberOne, boardMemberTwo]; - const transaction = factory.createTransactionForMultisigDeploy(senderAddress, { + const transaction = factory.createTransactionForDeploy(senderAddress, { bytecode: bytecode.valueOf(), gasLimit: 5000000n, quorum: 2, @@ -305,7 +305,7 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), - "proposeSCDeployFromSource@b1a2bc2ec50000@00000000000000000500870d0412cede871853a1c2d48a7543c073eb39f969e1@0500@7", + "proposeSCDeployFromSource@b1a2bc2ec50000@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@0500@37", ); }); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 1ecd4e2ef..ec2d7dd33 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -3,6 +3,8 @@ import { AddressValue, ArgSerializer, BigUIntValue, + BytesValue, + CodeMetadataValue, EndpointDefinition, EndpointModifiers, NativeSerializer, @@ -21,26 +23,38 @@ import { SmartContractTransactionsFactory } from "../smartContracts"; import { ProposeTransferExecuteContractInput } from "./proposeTransferExecuteContractInput"; import * as resources from "./resources"; +interface IConfig { + chainID: string; + addressHrp: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; +} + /** * Use this class to create multisig related transactions like creating a new multisig contract, * proposing actions, signing actions, and performing actions. */ -export class MultisigTransactionsFactory extends SmartContractTransactionsFactory { +export class MultisigTransactionsFactory { private readonly argSerializer: ArgSerializer; + private readonly smartContractFactory: SmartContractTransactionsFactory; + private readonly config: IConfig; + private readonly abi: Abi; constructor(options: { config: TransactionsFactoryConfig; abi: Abi }) { - super(options); + this.config = options.config; + this.abi = options.abi; this.argSerializer = new ArgSerializer(); + this.smartContractFactory = new SmartContractTransactionsFactory(options); } /** * Creates a transaction to deploy a new multisig contract */ - createTransactionForMultisigDeploy(sender: Address, options: resources.DeployMultisigContractInput): Transaction { + createTransactionForDeploy(sender: Address, options: resources.DeployMultisigContractInput): Transaction { const boardAddresses: AddressValue[] = options.board.map((addr) => new AddressValue(addr)); const args = [new U32Value(options.quorum), VariadicValue.fromItems(...boardAddresses)]; - return this.createTransactionForDeploy(sender, { + return this.smartContractFactory.createTransactionForDeploy(sender, { bytecode: options.bytecode, gasLimit: options.gasLimit, isUpgradeable: options.isUpgradeable, @@ -58,76 +72,48 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor sender: Address, options: resources.ProposeAddBoardMemberInput, ): Transaction { - const dataParts = [ - "proposeAddBoardMember", - this.argSerializer.valuesToStrings([new AddressValue(options.boardMember)])[0], - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeAddBoardMember", + gasLimit: options.gasLimit ?? 0n, + arguments: [new AddressValue(options.boardMember)], + }); } /** * Proposes adding a new proposer */ createTransactionForProposeAddProposer(sender: Address, options: resources.ProposeAddProposerInput): Transaction { - const dataParts = [ - "proposeAddProposer", - this.argSerializer.valuesToStrings([new AddressValue(options.proposer)])[0], - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeAddProposer", + gasLimit: options.gasLimit ?? 0n, + arguments: [new AddressValue(options.proposer)], + }); } /** * Proposes removing a user (board member or proposer) */ createTransactionForProposeRemoveUser(sender: Address, options: resources.ProposeRemoveUserInput): Transaction { - const dataParts = [ - "proposeRemoveUser", - this.argSerializer.valuesToStrings([new AddressValue(options.userAddress)])[0], - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeRemoveUser", + gasLimit: options.gasLimit ?? 0n, + arguments: [new AddressValue(options.userAddress)], + }); } /** * Proposes changing the quorum (minimum signatures required) */ createTransactionForProposeChangeQuorum(sender: Address, options: resources.ProposeChangeQuorumInput): Transaction { - const dataParts = [ - "proposeChangeQuorum", - this.argSerializer.valuesToStrings([new U32Value(options.newQuorum)])[0], - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeChangeQuorum", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.newQuorum)], + }); } /** @@ -145,31 +131,25 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor arguments: options.functionArguments, abi: options.abi, }); - const dataParts = [ - "proposeTransferExecute", - ...this.argSerializer.valuesToStrings([ + + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeTransferExecute", + gasLimit: options.gasLimit ?? 0n, + arguments: [ new AddressValue(options.to), new BigUIntValue(options.nativeTokenAmount), new OptionValue(new OptionType(new U64Type()), gasOption), - ]), - ...input.functionCall, - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + VariadicValue.fromItems(...input.functionCall.map((value) => new BytesValue(value))), + ], + }); } /** * Proposes a transaction that will transfer EGLD and/or execute a function */ createTransactionForDeposit(sender: Address, options: resources.DepositExecuteInput): Transaction { - return this.createTransactionForExecute(sender, { + return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "deposit", gasLimit: options.gasLimit ?? 0n, @@ -240,26 +220,18 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor arguments: options.functionArguments, abi: options.abi, }); - let receiver = options.multisigContract; - const dataParts = [ - "proposeAsyncCall", - ...this.argSerializer.valuesToStrings([ + + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeAsyncCall", + gasLimit: options.gasLimit ?? 0n, + arguments: [ new AddressValue(options.to), new BigUIntValue(options.nativeTransferAmount), new BigUIntValue(options.gasLimit ?? 0n), - ]), - ...input.functionCall, - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: receiver, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - amount: 0n, - }).build(); + VariadicValue.fromItems(...input.functionCall.map((value) => new BytesValue(value))), + ], + }); } /** @@ -269,21 +241,17 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor sender: Address, options: resources.ProposeContractDeployFromSourceInput, ): Transaction { - const dataParts = [ - "proposeSCDeployFromSource", - ...this.argSerializer.valuesToStrings([new BigUIntValue(options.amount), new AddressValue(options.source)]), - options.codeMetadata.toString(), - ...options.arguments, - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeSCDeployFromSource", + gasLimit: options.gasLimit ?? 0n, + arguments: [ + new BigUIntValue(options.amount), + new AddressValue(options.multisigContract), + new CodeMetadataValue(options.codeMetadata), + VariadicValue.fromItems(...options.arguments.map((value) => new BytesValue(Buffer.from(value)))), + ], + }); } /** @@ -293,124 +261,90 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor sender: Address, options: resources.ProposeContractUpgradeFromSourceInput, ): Transaction { - const dataParts = [ - "proposeSCUpgradeFromSource", - ...this.argSerializer.valuesToStrings([ - new AddressValue(options.scAddress), + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "proposeSCUpgradeFromSource", + gasLimit: options.gasLimit ?? 0n, + arguments: [ + new AddressValue(options.multisigContract), new BigUIntValue(options.amount), new AddressValue(options.source), - ]), - options.codeMetadata.toString(), - ...options.arguments, - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + new CodeMetadataValue(options.codeMetadata), + VariadicValue.fromItems(...options.arguments.map((value) => new BytesValue(Buffer.from(value)))), + ], + }); } /** * Signs an action (by a board member) */ createTransactionForSignAction(sender: Address, options: resources.ActionInput): Transaction { - const dataParts = ["sign", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "sign", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.actionId)], + }); } /** * Signs all actions in a batch */ createTransactionForSignBatch(sender: Address, options: resources.GroupInput): Transaction { - const dataParts = ["signBatch", this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "signBatch", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.groupId)], + }); } /** * Signs and performs an action in one transaction */ createTransactionForSignAndPerform(sender: Address, options: resources.ActionInput): Transaction { - const dataParts = ["signAndPerform", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "signAndPerform", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.actionId)], + }); } /** * Signs and performs all actions in a batch */ createTransactionForSignBatchAndPerform(sender: Address, options: resources.GroupInput): Transaction { - const dataParts = [ - "signBatchAndPerform", - this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0], - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "signBatchAndPerform", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.groupId)], + }); } /** * Withdraws signature from an action */ createTransactionForUnsign(sender: Address, options: resources.ActionInput): Transaction { - const dataParts = ["unsign", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "unsign", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.actionId)], + }); } /** * Withdraws signatures from all actions in a batch */ createTransactionForUnsignBatch(sender: Address, options: resources.GroupInput): Transaction { - const dataParts = ["unsignBatch", this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "unsignBatch", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.groupId)], + }); } /** @@ -420,89 +354,61 @@ export class MultisigTransactionsFactory extends SmartContractTransactionsFactor sender: Address, options: resources.UnsignForOutdatedBoardMembersInput, ): Transaction { - const outdatedMembersArgs = options.outdatedBoardMembers.map( - (id) => this.argSerializer.valuesToStrings([new U32Value(id)])[0], - ); - - const dataParts = [ - "unsignForOutdatedBoardMembers", - this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0], - ...outdatedMembersArgs, - ]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + const outdatedBoardMembers: U32Value[] = options.outdatedBoardMembers.map((id) => new U32Value(id)); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "unsignForOutdatedBoardMembers", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.actionId), VariadicValue.fromItems(...outdatedBoardMembers)], + }); } /** * Performs an action that has reached quorum */ createTransactionForPerformAction(sender: Address, options: resources.ActionInput): Transaction { - const dataParts = ["performAction", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "performAction", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.actionId)], + }); } /** * Performs all actions in a batch that have reached quorum */ createTransactionForPerformBatch(sender: Address, options: resources.GroupInput): Transaction { - const dataParts = ["performBatch", this.argSerializer.valuesToStrings([new U32Value(options.groupId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "performBatch", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.groupId)], + }); } /** * Discards an action that is no longer needed */ createTransactionForDiscardAction(sender: Address, options: resources.ActionInput): Transaction { - const dataParts = ["discardAction", this.argSerializer.valuesToStrings([new U32Value(options.actionId)])[0]]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "discardAction", + gasLimit: options.gasLimit ?? 0n, + arguments: [new U32Value(options.actionId)], + }); } /** * Discards all actions in the provided list */ createTransactionForDiscardBatch(sender: Address, options: resources.DiscardBatchInput): Transaction { - const actionIdsArgs = options.actionIds.map((id) => this.argSerializer.valuesToStrings([new U32Value(id)])[0]); - - const dataParts = ["discardBatch", ...actionIdsArgs]; - - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + const actionIdsArgs = options.actionIds.map((id) => new U32Value(id)); + return this.smartContractFactory.createTransactionForExecute(sender, { + contract: options.multisigContract, + function: "discardBatch", + gasLimit: options.gasLimit ?? 0n, + arguments: [VariadicValue.fromItems(...actionIdsArgs)], + }); } } diff --git a/src/smartContracts/smartContractTransactionsFactory.ts b/src/smartContracts/smartContractTransactionsFactory.ts index 5fc334322..14a0249e1 100644 --- a/src/smartContracts/smartContractTransactionsFactory.ts +++ b/src/smartContracts/smartContractTransactionsFactory.ts @@ -23,8 +23,8 @@ interface IConfig { * Use this class to create transactions to deploy, call or upgrade a smart contract. */ export class SmartContractTransactionsFactory { - protected readonly config: IConfig; - protected readonly abi?: Abi; + private readonly config: IConfig; + private readonly abi?: Abi; private readonly tokenComputer: TokenComputer; private readonly dataArgsBuilder: TokenTransfersDataBuilder; private readonly contractDeployAddress: Address; From 99804cc34af92de8f2b968fb7fc6e4bc304d5b7e Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 10:26:00 +0300 Subject: [PATCH 39/54] Fix deploy and upgrade from source --- .../multisigTransactionsFactory.spec.ts | 85 +++++++++++++++++-- src/multisig/multisigTransactionsFactory.ts | 27 +++++- src/multisig/resources.ts | 6 +- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index 525da2114..50cc61137 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Abi, Code } from "../abi"; +import { Abi, AddressValue, BigUIntValue, Code, U32Value, VariadicValue } from "../abi"; import { CodeMetadata, Token, TokenTransfer } from "../core"; import { Address } from "../core/address"; import { Transaction } from "../core/transaction"; @@ -15,11 +15,13 @@ describe("test multisig transactions factory", function () { let bytecode: Code; let abi: Abi; let adderAbi: Abi; + let esdtSafeAbi: Abi; let factory: MultisigTransactionsFactory; before(async function () { bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); adderAbi = await loadAbiRegistry("src/testdata/adder.abi.json"); + esdtSafeAbi = await loadAbiRegistry("src/testdata/esdt-safe.abi.json"); factory = new MultisigTransactionsFactory({ config: config, @@ -281,7 +283,7 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "ESDTTransfer@414c4943452d353632376631@64@6465706f736974"); }); - it("should create transaction for propose SC deploy from source", function () { + it("should create transaction for propose SC deploy from source when abi is passed", function () { const amount = BigInt(50000000000000000); // 0.05 EGLD const metadata = new CodeMetadata(true, true, false); const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -297,6 +299,7 @@ describe("test multisig transactions factory", function () { source: sourceContract, codeMetadata: metadata, arguments: ["7"], + abi: adderAbi, }); assert.instanceOf(transaction, Transaction); @@ -305,11 +308,73 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), - "proposeSCDeployFromSource@b1a2bc2ec50000@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@0500@37", + "proposeSCDeployFromSource@b1a2bc2ec50000@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@0500@07", + ); + }); + + it("should create transaction for propose SC deploy from source when no abi is passed", function () { + const amount = BigInt(50000000000000000); // 0.05 EGLD + const metadata = new CodeMetadata(true, true, false); + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const sourceContract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqsuxsgykwm6r3s5apct2g5a2rcpe7kw0ed8ssf6h9f6"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", + ); + + const transaction = factory.createTransactionForProposeContractDeployFromSource(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + amount: amount, + source: sourceContract, + codeMetadata: metadata, + arguments: [new BigUIntValue(7n)], + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeSCDeployFromSource@b1a2bc2ec50000@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@0500@07", + ); + }); + + it("should create transaction for propose SC upgrade from source when abi is passed", function () { + const amount = BigInt(50000000000000000); // 0.05 EGLD + const metadata = new CodeMetadata(true, true, false); + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const sourceContract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqd273cw3hjndqzcpts4dvq0ncy8nx8rkgzeusnefvaq"); + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", + ); + + const transaction = factory.createTransactionForProposeContractUpgradeFromSource(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 5000000n, + scAddress: multisigContractAddress, + amount: amount, + source: sourceContract, + codeMetadata: metadata, + arguments: [ + 2, + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "erd1qqqqqqqqqqqqqpgqsuxsgykwm6r3s5apct2g5a2rcpe7kw0ed8ssf6h9f6", + ], + abi: esdtSafeAbi, + }); + + assert.instanceOf(transaction, Transaction); + assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); + assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); + assert.equal(transaction.chainID, config.chainID); + assert.deepEqual( + transaction.data.toString(), + "proposeSCUpgradeFromSource@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@b1a2bc2ec50000@000000000000000005006abd1c3a3794da01602b855ac03e7821e6638ec81679@0500", ); }); - it("should create transaction for propose SC upgrade from source", function () { + it("should create transaction for propose SC upgrade from source when no abi is passed", function () { const amount = BigInt(50000000000000000); // 0.05 EGLD const metadata = new CodeMetadata(true, true, false); const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -325,7 +390,17 @@ describe("test multisig transactions factory", function () { amount: amount, source: sourceContract, codeMetadata: metadata, - arguments: [], + arguments: [ + new U32Value(2n), + VariadicValue.fromItems( + new AddressValue( + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ), + new AddressValue( + Address.newFromBech32("erd1qqqqqqqqqqqqqpgqsuxsgykwm6r3s5apct2g5a2rcpe7kw0ed8ssf6h9f6"), + ), + ), + ], }); assert.instanceOf(transaction, Transaction); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index ec2d7dd33..d86e87b34 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -7,15 +7,17 @@ import { CodeMetadataValue, EndpointDefinition, EndpointModifiers, + isTyped, NativeSerializer, OptionType, OptionValue, + TypedValue, U32Value, U64Type, U64Value, VariadicValue, } from "../abi"; -import { TokenComputer, TransactionsFactoryConfig } from "../core"; +import { Err, TokenComputer, TransactionsFactoryConfig } from "../core"; import { Address } from "../core/address"; import { Transaction } from "../core/transaction"; import { TransactionBuilder } from "../core/transactionBuilder"; @@ -241,6 +243,7 @@ export class MultisigTransactionsFactory { sender: Address, options: resources.ProposeContractDeployFromSourceInput, ): Transaction { + let args: TypedValue[] = this.argsToTypedValues(options.arguments, options.abi?.constructorDefinition); return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeSCDeployFromSource", @@ -249,7 +252,7 @@ export class MultisigTransactionsFactory { new BigUIntValue(options.amount), new AddressValue(options.multisigContract), new CodeMetadataValue(options.codeMetadata), - VariadicValue.fromItems(...options.arguments.map((value) => new BytesValue(Buffer.from(value)))), + VariadicValue.fromItems(...args), ], }); } @@ -261,6 +264,7 @@ export class MultisigTransactionsFactory { sender: Address, options: resources.ProposeContractUpgradeFromSourceInput, ): Transaction { + let args: TypedValue[] = this.argsToTypedValues(options.arguments, options.abi?.constructorDefinition); return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeSCUpgradeFromSource", @@ -270,7 +274,7 @@ export class MultisigTransactionsFactory { new BigUIntValue(options.amount), new AddressValue(options.source), new CodeMetadataValue(options.codeMetadata), - VariadicValue.fromItems(...options.arguments.map((value) => new BytesValue(Buffer.from(value)))), + VariadicValue.fromItems(...args), ], }); } @@ -411,4 +415,21 @@ export class MultisigTransactionsFactory { arguments: [VariadicValue.fromItems(...actionIdsArgs)], }); } + + protected argsToTypedValues(args: any[], endpoint?: EndpointDefinition): TypedValue[] { + if (endpoint) { + const typedArgs = NativeSerializer.nativeToTypedValues(args, endpoint); + return typedArgs; + } + + if (this.areArgsOfTypedValue(args)) { + return args; + } + + throw new Err("Can't convert args to TypedValues"); + } + + private areArgsOfTypedValue(args: any[]): boolean { + return args.every((arg) => isTyped(arg)); + } } diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 1b06a9aa4..754400ae8 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -74,7 +74,8 @@ export type ProposeContractDeployFromSourceInput = MultisigContractInput & { amount: bigint; source: Address; codeMetadata: CodeMetadata; - arguments: string[]; + arguments: any[]; + abi?: Abi; }; export type ProposeContractUpgradeFromSourceInput = MultisigContractInput & { @@ -82,7 +83,8 @@ export type ProposeContractUpgradeFromSourceInput = MultisigContractInput & { amount: bigint; source: Address; codeMetadata: CodeMetadata; - arguments: string[]; + arguments: any[]; + abi?: Abi; }; export type ActionInput = MultisigContractInput & { From 034d18c29bf81c65c82ccbc2e129492165a6c15b Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 10:26:20 +0300 Subject: [PATCH 40/54] Add await and parse methods --- src/multisig/multisigController.ts | 156 ++++------------------------- 1 file changed, 20 insertions(+), 136 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 6a38aa658..fb19e6339 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -6,11 +6,12 @@ import { BaseControllerInput, IAccount, Transaction, + TransactionOnNetwork, TransactionsFactoryConfig, TransactionWatcher, } from "../core"; import { INetworkProvider } from "../networkProviders/interface"; -import { SmartContractController } from "../smartContracts"; +import { SmartContractController, SmartContractDeployOutcome } from "../smartContracts"; import { MultisigTransactionsFactory } from "./multisigTransactionsFactory"; import { MultisigTransactionsOutcomeParser } from "./multisigTransactionsOutcomeParser"; import * as resources from "./resources"; @@ -55,6 +56,15 @@ export class MultisigController extends BaseController { return transaction; } + parseDeploy(transactionOnNetwork: TransactionOnNetwork): SmartContractDeployOutcome { + return this.multisigParser.parseDeploy(transactionOnNetwork); + } + + async awaitCompletedDeploy(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.parseDeploy(transaction); + } + /** * Gets quorum for specific multisig */ @@ -313,14 +323,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to add a proposer */ @@ -340,14 +342,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to remove a user */ @@ -367,14 +361,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to change quorum */ @@ -394,10 +380,11 @@ export class MultisigController extends BaseController { return transaction; } - /** - * Awaits the completion of a propose change quorum action - */ - async awaitCompletedProposeChangeQuorum(txHash: string): Promise { + parseProposeAction(transaction: TransactionOnNetwork): number { + return this.multisigParser.parseProposeAction(transaction); + } + + async awaitCompletedProposeAction(txHash: string): Promise { const transaction = await this.transactionAwaiter.awaitCompleted(txHash); return this.multisigParser.parseProposeAction(transaction); } @@ -421,13 +408,6 @@ export class MultisigController extends BaseController { 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 */ @@ -447,9 +427,10 @@ export class MultisigController extends BaseController { return transaction; } - /** - * Awaits the completion of a perform action - */ + parsePerformAction(transaction: TransactionOnNetwork): Address | undefined { + return this.multisigParser.parsePerformAction(transaction); + } + async awaitCompletedPerformAction(txHash: string): Promise
{ const transaction = await this.transactionAwaiter.awaitCompleted(txHash); return this.multisigParser.parsePerformAction(transaction); @@ -474,13 +455,6 @@ export class MultisigController extends BaseController { 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 */ @@ -500,13 +474,6 @@ export class MultisigController extends BaseController { 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 */ @@ -526,14 +493,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to transfer EGLD and execute a smart contract call */ @@ -553,14 +512,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to transfer ESDT tokens and execute a smart contract call */ @@ -583,14 +534,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing an async call to another contract */ @@ -610,14 +553,6 @@ export class MultisigController extends BaseController { 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.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to deploy a smart contract from source */ @@ -640,14 +575,6 @@ export class MultisigController extends BaseController { return transaction; } - /** - * Awaits the completion of a propose Contract deploy from source action - */ - async awaitCompletedProposeContractDeployFromSource(txHash: string): Promise { - const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseProposeAction(transaction); - } - /** * Creates a transaction for proposing to upgrade a smart contract from source */ @@ -670,14 +597,6 @@ export class MultisigController extends BaseController { return transaction; } - /** - * Awaits the completion of a propose Contract upgrade from source action - */ - async awaitCompletedProposeContractUpgradeFromSource(txHash: string): Promise { - const transaction = await this.transactionAwaiter.awaitCompleted(txHash); - return this.multisigParser.parseProposeAction(transaction); - } - /** * Creates a transaction for signing a batch of actions */ @@ -697,13 +616,6 @@ export class MultisigController extends BaseController { 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 */ @@ -723,13 +635,6 @@ export class MultisigController extends BaseController { 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 */ @@ -752,13 +657,6 @@ export class MultisigController extends BaseController { 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 */ @@ -778,13 +676,6 @@ export class MultisigController extends BaseController { 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 */ @@ -804,13 +695,6 @@ export class MultisigController extends BaseController { 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) { From 11d3ae8d9b390bce46c542dbfd99d68d124bcf3a Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 10:29:17 +0300 Subject: [PATCH 41/54] fix test --- 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 50cc61137..c2b61d1b6 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -370,7 +370,7 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), - "proposeSCUpgradeFromSource@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@b1a2bc2ec50000@000000000000000005006abd1c3a3794da01602b855ac03e7821e6638ec81679@0500", + "proposeSCUpgradeFromSource@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@b1a2bc2ec50000@000000000000000005006abd1c3a3794da01602b855ac03e7821e6638ec81679@0500@02@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@00000000000000000500870d0412cede871853a1c2d48a7543c073eb39f969e1", ); }); @@ -409,7 +409,7 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.chainID, config.chainID); assert.deepEqual( transaction.data.toString(), - "proposeSCUpgradeFromSource@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@b1a2bc2ec50000@000000000000000005006abd1c3a3794da01602b855ac03e7821e6638ec81679@0500", + "proposeSCUpgradeFromSource@000000000000000005007e25ce6debac748d86b5d393120ab1eb02a46d581679@b1a2bc2ec50000@000000000000000005006abd1c3a3794da01602b855ac03e7821e6638ec81679@0500@02@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@00000000000000000500870d0412cede871853a1c2d48a7543c073eb39f969e1", ); }); From 17942d32d0c494122e74d00e8410d2fa92011e78 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 11:28:17 +0300 Subject: [PATCH 42/54] Update types on gasLimit and address --- src/multisig/multisigController.ts | 2 +- src/multisig/resources.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index fb19e6339..a2efb38c5 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -256,7 +256,7 @@ export class MultisigController extends BaseController { 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()), + signers: element.signers.map((address: Address) => new Address(address)), }); } return result; diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 754400ae8..0e43f757a 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -174,7 +174,7 @@ export class ChangeQuorum extends MultisigAction { export class SendTransferExecuteEgld extends MultisigAction { receiver: Address; amount: bigint; - optionalGasLimit?: number; + optionalGasLimit?: bigint; funcionName: string; arguments: Uint8Array[]; @@ -182,8 +182,8 @@ export class SendTransferExecuteEgld extends MultisigAction { super(); this.type = MultisigActionEnum.SendTransferExecuteEgld; this.receiver = data.to; - this.amount = data.egld_amount; - this.optionalGasLimit = data.opt_gas_limit; + this.amount = BigInt(data.egld_amount?.toString() ?? 0); + this.optionalGasLimit = BigInt(data.opt_gas_limit?.toString() ?? 0); this.funcionName = data.endpoint_name.toString(); this.arguments = data.arguments; } @@ -191,7 +191,7 @@ export class SendTransferExecuteEgld extends MultisigAction { export class SendTransferExecuteEsdt extends MultisigAction { receiver: Address; tokens: TokenTransfer[]; - optionalGasLimit?: number; + optionalGasLimit?: bigint; funcionName: string; arguments: Uint8Array[]; @@ -206,7 +206,7 @@ export class SendTransferExecuteEsdt extends MultisigAction { amount: token.amount, }), ); - this.optionalGasLimit = data.opt_gas_limit; + this.optionalGasLimit = BigInt(data.opt_gas_limit.toString()); this.funcionName = Buffer.from(data.endpoint_name.toString(), "hex").toString(); this.arguments = data.arguments; @@ -216,7 +216,7 @@ export class SendTransferExecuteEsdt extends MultisigAction { export class SendAsyncCall extends MultisigAction { receiver: Address; amount: bigint; - optionalGasLimit?: number; + optionalGasLimit?: bigint; funcionName: string; arguments: Uint8Array[]; @@ -224,8 +224,8 @@ export class SendAsyncCall extends MultisigAction { super(); this.type = MultisigActionEnum.SendAsyncCall; this.receiver = data.to; - this.amount = data.egld_amount; - this.optionalGasLimit = data.opt_gas_limit; + this.amount = BigInt(data.egld_amount?.toString() ?? 0); + this.optionalGasLimit = BigInt(data.opt_gas_limit?.toString() ?? 0); this.funcionName = data.endpoint_name.toString(); this.arguments = data.arguments; } From 386ec8d141fe61ac48262545b69a18cf4cb105f3 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 11:36:20 +0300 Subject: [PATCH 43/54] Update to use optGasLimit when passed --- src/multisig/multisigTransactionsFactory.ts | 6 +++--- src/multisig/resources.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index d86e87b34..2f916c7f2 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -125,7 +125,7 @@ export class MultisigTransactionsFactory { sender: Address, options: resources.ProposeTransferExecuteInput, ): Transaction { - const gasOption = new U64Value(options.gasLimit); + const gasOption = new U64Value(options.optGasLimit ?? 0n); const input = ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ multisig: options.multisigContract, to: options.to, @@ -181,7 +181,7 @@ export class MultisigTransactionsFactory { "proposeTransferExecuteEsdt", ...this.argSerializer.valuesToStrings( NativeSerializer.nativeToTypedValues( - [options.to, tokenPayments, options.gasLimit, VariadicValue.fromItems(...input.functionCall)], + [options.to, tokenPayments, options.optGasLimit, VariadicValue.fromItems(...input.functionCall)], this.abi?.getEndpoint("proposeTransferExecuteEsdt") ?? new EndpointDefinition("proposeTransferExecuteEsdt", [], [], new EndpointModifiers("", [])), ), @@ -230,7 +230,7 @@ export class MultisigTransactionsFactory { arguments: [ new AddressValue(options.to), new BigUIntValue(options.nativeTransferAmount), - new BigUIntValue(options.gasLimit ?? 0n), + new BigUIntValue(options.optGasLimit ?? 0n), VariadicValue.fromItems(...input.functionCall.map((value) => new BytesValue(value))), ], }); diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 0e43f757a..2b600af36 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -66,7 +66,7 @@ export type ProposeAsyncCallInput = MultisigContractInput & { tokenTransfers: TokenTransfer[]; functionName: string; functionArguments: any[]; - gasLimit: bigint; + optGasLimit?: bigint; abi?: Abi; }; From 695f9b0a02e61d1788d105d32ed2059fa575c42d Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 11:37:26 +0300 Subject: [PATCH 44/54] 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 03972f656..ab7236ec0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.5", + "version": "14.1.0-beta.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.5", + "version": "14.1.0-beta.6", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index da8a7a8c4..64daacfcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.1.0-beta.5", + "version": "14.1.0-beta.6", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From 034cbee1f95a0985b8f76af8354c6fc55080a7ff Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 12:00:31 +0300 Subject: [PATCH 45/54] Update factory --- .../multisigTransactionsFactory.spec.ts | 3 + src/multisig/multisigTransactionsFactory.ts | 70 +++++++++---------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index c2b61d1b6..068a05ddd 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -160,6 +160,7 @@ describe("test multisig transactions factory", function () { to: destinationContract, functionName: "add", functionArguments: [7], + optGasLimit: 5000000n, abi: adderAbi, }); @@ -194,6 +195,7 @@ describe("test multisig transactions factory", function () { tokens: [tokenTransfer], functionName: "distribute", functionArguments: [], + optGasLimit: 5000000n, }); assert.instanceOf(transaction, Transaction); @@ -223,6 +225,7 @@ describe("test multisig transactions factory", function () { functionArguments: [7], tokenTransfers: [], abi: adderAbi, + optGasLimit: 5000000n, }); assert.instanceOf(transaction, Transaction); diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index 2f916c7f2..c2997776f 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -6,7 +6,6 @@ import { BytesValue, CodeMetadataValue, EndpointDefinition, - EndpointModifiers, isTyped, NativeSerializer, OptionType, @@ -77,8 +76,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeAddBoardMember", - gasLimit: options.gasLimit ?? 0n, - arguments: [new AddressValue(options.boardMember)], + gasLimit: options.gasLimit, + arguments: [options.boardMember], }); } @@ -89,8 +88,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeAddProposer", - gasLimit: options.gasLimit ?? 0n, - arguments: [new AddressValue(options.proposer)], + gasLimit: options.gasLimit, + arguments: [options.proposer], }); } @@ -101,8 +100,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeRemoveUser", - gasLimit: options.gasLimit ?? 0n, - arguments: [new AddressValue(options.userAddress)], + gasLimit: options.gasLimit, + arguments: [options.userAddress], }); } @@ -113,8 +112,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeChangeQuorum", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.newQuorum)], + gasLimit: options.gasLimit, + arguments: [options.newQuorum], }); } @@ -137,7 +136,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeTransferExecute", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [ new AddressValue(options.to), new BigUIntValue(options.nativeTokenAmount), @@ -154,7 +153,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "deposit", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [], nativeTransferAmount: options.nativeTokenAmount, tokenTransfers: options.tokenTransfers, @@ -182,8 +181,7 @@ export class MultisigTransactionsFactory { ...this.argSerializer.valuesToStrings( NativeSerializer.nativeToTypedValues( [options.to, tokenPayments, options.optGasLimit, VariadicValue.fromItems(...input.functionCall)], - this.abi?.getEndpoint("proposeTransferExecuteEsdt") ?? - new EndpointDefinition("proposeTransferExecuteEsdt", [], [], new EndpointModifiers("", [])), + this.abi.getEndpoint("proposeTransferExecuteEsdt"), ), ), ]; @@ -226,7 +224,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeAsyncCall", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [ new AddressValue(options.to), new BigUIntValue(options.nativeTransferAmount), @@ -247,7 +245,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeSCDeployFromSource", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [ new BigUIntValue(options.amount), new AddressValue(options.multisigContract), @@ -268,7 +266,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeSCUpgradeFromSource", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [ new AddressValue(options.multisigContract), new BigUIntValue(options.amount), @@ -286,8 +284,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "sign", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.actionId)], + gasLimit: options.gasLimit, + arguments: [options.actionId], }); } @@ -298,8 +296,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "signBatch", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.groupId)], + gasLimit: options.gasLimit, + arguments: [options.groupId], }); } @@ -310,8 +308,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "signAndPerform", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.actionId)], + gasLimit: options.gasLimit, + arguments: [options.actionId], }); } @@ -322,8 +320,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "signBatchAndPerform", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.groupId)], + gasLimit: options.gasLimit, + arguments: [options.groupId], }); } @@ -334,8 +332,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "unsign", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.actionId)], + gasLimit: options.gasLimit, + arguments: [options.actionId], }); } @@ -346,8 +344,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "unsignBatch", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.groupId)], + gasLimit: options.gasLimit, + arguments: [options.groupId], }); } @@ -362,7 +360,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "unsignForOutdatedBoardMembers", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [new U32Value(options.actionId), VariadicValue.fromItems(...outdatedBoardMembers)], }); } @@ -374,8 +372,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "performAction", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.actionId)], + gasLimit: options.gasLimit, + arguments: [options.actionId], }); } @@ -386,8 +384,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "performBatch", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.groupId)], + gasLimit: options.gasLimit, + arguments: [options.groupId], }); } @@ -398,8 +396,8 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "discardAction", - gasLimit: options.gasLimit ?? 0n, - arguments: [new U32Value(options.actionId)], + gasLimit: options.gasLimit, + arguments: [options.actionId], }); } @@ -411,7 +409,7 @@ export class MultisigTransactionsFactory { return this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "discardBatch", - gasLimit: options.gasLimit ?? 0n, + gasLimit: options.gasLimit, arguments: [VariadicValue.fromItems(...actionIdsArgs)], }); } From 60d15bf257b8b6202609d0b30f48a2a1ba5809b7 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 7 May 2025 15:14:22 +0300 Subject: [PATCH 46/54] Delete unused files --- src/networkProviders/contractQueryResponse.ts | 50 ------------ src/networkProviders/contractResults.ts | 79 ------------------- src/networkProviders/index.ts | 3 - 3 files changed, 132 deletions(-) delete mode 100644 src/networkProviders/contractQueryResponse.ts delete mode 100644 src/networkProviders/contractResults.ts diff --git a/src/networkProviders/contractQueryResponse.ts b/src/networkProviders/contractQueryResponse.ts deleted file mode 100644 index 40da09129..000000000 --- a/src/networkProviders/contractQueryResponse.ts +++ /dev/null @@ -1,50 +0,0 @@ -import BigNumber from "bignumber.js"; -import { MaxUint64AsBigNumber } from "./constants"; - -export class ContractQueryResponse { - returnData: string[]; - returnCode: string; - returnMessage: string; - gasUsed: number; - - constructor(init?: Partial) { - this.returnData = init?.returnData || []; - this.returnCode = init?.returnCode || ""; - this.returnMessage = init?.returnMessage || ""; - this.gasUsed = init?.gasUsed || 0; - } - - /** - * Constructs a QueryResponse object from a HTTP response (as returned by the provider). - */ - static fromHttpResponse(payload: any): ContractQueryResponse { - let returnData = payload["returnData"] || payload["ReturnData"]; - let returnCode = payload["returnCode"] || payload["ReturnCode"]; - let returnMessage = payload["returnMessage"] || payload["ReturnMessage"]; - let gasRemaining = new BigNumber(payload["gasRemaining"] || payload["GasRemaining"] || 0); - let gasUsed = MaxUint64AsBigNumber.minus(gasRemaining).toNumber(); - - return new ContractQueryResponse({ - returnData: returnData, - returnCode: returnCode, - returnMessage: returnMessage, - gasUsed: gasUsed, - }); - } - - getReturnDataParts(): Buffer[] { - return this.returnData.map((item) => Buffer.from(item || "", "base64")); - } - - /** - * Converts the object to a pretty, plain JavaScript object. - */ - toJSON(): object { - return { - returnData: this.returnData, - returnCode: this.returnCode, - returnMessage: this.returnMessage, - gasUsed: this.gasUsed.valueOf(), - }; - } -} diff --git a/src/networkProviders/contractResults.ts b/src/networkProviders/contractResults.ts deleted file mode 100644 index 38c057f75..000000000 --- a/src/networkProviders/contractResults.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Address } from "../core/address"; -import { TransactionLogs } from "../core/transactionLogs"; - -export class ContractResults { - readonly items: ContractResultItem[]; - - constructor(items: ContractResultItem[]) { - this.items = items; - - this.items.sort(function (a: ContractResultItem, b: ContractResultItem) { - return a.nonce.valueOf() - b.nonce.valueOf(); - }); - } - - static fromProxyHttpResponse(results: any[]): ContractResults { - const items = results.map((item) => ContractResultItem.fromProxyHttpResponse(item)); - return new ContractResults(items); - } - - static fromApiHttpResponse(results: any[]): ContractResults { - let items = results.map((item) => ContractResultItem.fromApiHttpResponse(item)); - return new ContractResults(items); - } -} - -export class ContractResultItem { - hash: string = ""; - nonce: number = 0; - value: string = ""; - receiver: Address = Address.empty(); - sender: Address = Address.empty(); - data: string = ""; - previousHash: string = ""; - originalHash: string = ""; - gasLimit: number = 0; - gasPrice: number = 0; - callType: number = 0; - returnMessage: string = ""; - logs: TransactionLogs = new TransactionLogs(); - - constructor(init?: Partial) { - Object.assign(this, init); - } - - static fromProxyHttpResponse(response: any): ContractResultItem { - const item = ContractResultItem.fromHttpResponse(response); - return item; - } - - static fromApiHttpResponse(response: any): ContractResultItem { - let item = ContractResultItem.fromHttpResponse(response); - - item.data = Buffer.from(item.data, "base64").toString(); - item.callType = Number(item.callType); - - return item; - } - - private static fromHttpResponse(response: any): ContractResultItem { - let item = new ContractResultItem(); - - item.hash = response.hash; - item.nonce = Number(response.nonce || 0); - item.value = (response.value || 0).toString(); - item.receiver = new Address(response.receiver); - item.sender = new Address(response.sender); - item.previousHash = response.prevTxHash; - item.originalHash = response.originalTxHash; - item.gasLimit = Number(response.gasLimit || 0); - item.gasPrice = Number(response.gasPrice || 0); - item.data = response.data || ""; - item.callType = response.callType; - item.returnMessage = response.returnMessage; - - item.logs = TransactionLogs.fromHttpResponse(response.logs || {}); - - return item; - } -} diff --git a/src/networkProviders/index.ts b/src/networkProviders/index.ts index 97f5dffa9..3e11b80a4 100644 --- a/src/networkProviders/index.ts +++ b/src/networkProviders/index.ts @@ -2,9 +2,6 @@ export { ApiNetworkProvider } from "./apiNetworkProvider"; export * from "./interface"; export { ProxyNetworkProvider } from "./proxyNetworkProvider"; -export { ContractQueryResponse } from "./contractQueryResponse"; -export { ContractResultItem, ContractResults } from "./contractResults"; - export * from "./accounts"; export * from "./blocks"; export { NetworkConfig } from "./networkConfig"; From edad0e8bc0091c3e122b13ca8abc4bdfa04a453d Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 20 May 2025 16:10:56 +0300 Subject: [PATCH 47/54] governance transactions factory --- src/core/constants.ts | 1 + src/core/transactionsFactoryConfig.ts | 14 ++ .../governanceTransactionsFactory.spec.ts | 106 ++++++++++++++ .../governanceTransactionsFactory.ts | 133 ++++++++++++++++++ src/governance/index.ts | 2 + src/governance/resources.ts | 36 +++++ src/index.ts | 1 + 7 files changed, 293 insertions(+) create mode 100644 src/governance/governanceTransactionsFactory.spec.ts create mode 100644 src/governance/governanceTransactionsFactory.ts create mode 100644 src/governance/index.ts create mode 100644 src/governance/resources.ts diff --git a/src/core/constants.ts b/src/core/constants.ts index 765ed156c..b9dbfa59c 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -14,6 +14,7 @@ export const VM_TYPE_WASM_VM = new Uint8Array([0x05, 0x00]); export const CONTRACT_DEPLOY_ADDRESS_HEX = "0000000000000000000000000000000000000000000000000000000000000000"; export const DELEGATION_MANAGER_SC_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000004ffff"; export const ESDT_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000002ffff"; +export const GOVERNANCE_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000003ffff"; export const DEFAULT_MESSAGE_VERSION = 1; export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; diff --git a/src/core/transactionsFactoryConfig.ts b/src/core/transactionsFactoryConfig.ts index d898fbb00..a7b38372f 100644 --- a/src/core/transactionsFactoryConfig.ts +++ b/src/core/transactionsFactoryConfig.ts @@ -44,6 +44,12 @@ export class TransactionsFactoryConfig { gasLimitNftChangeToDynamic: bigint; gasLimitUpdateTokenId: bigint; gasLimitRegisterDynamic: bigint; + gasLimitForProposal: bigint; + gasLimitForVote: bigint; + gasLimitForClosingProposal: bigint; + gasLimitForClearProposals: bigint; + gasLimitForChangeConfig: bigint; + gasLimitForClaimAccumulatedFees: bigint; constructor(options: { chainID: string }) { // General-purpose configuration @@ -100,5 +106,13 @@ export class TransactionsFactoryConfig { // Configuration for smart contract operations this.gasLimitClaimDeveloperRewards = 6000000n; this.gasLimitChangeOwnerAddress = 6000000n; + + // Configuration for governance operations + this.gasLimitForProposal = 50_000_000n; + this.gasLimitForVote = 5_000_000n; + this.gasLimitForClosingProposal = 50_000_000n; + this.gasLimitForClearProposals = 50_000_000n; + this.gasLimitForChangeConfig = 50_000_000n; + this.gasLimitForClaimAccumulatedFees = 1_000_000n; } } diff --git a/src/governance/governanceTransactionsFactory.spec.ts b/src/governance/governanceTransactionsFactory.spec.ts new file mode 100644 index 000000000..784aadd3b --- /dev/null +++ b/src/governance/governanceTransactionsFactory.spec.ts @@ -0,0 +1,106 @@ +import { assert } from "chai"; +import { Address } from "../core/address"; +import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; +import { GovernanceTransactionsFactory } from "./governanceTransactionsFactory"; +import { Vote } from "./resources"; + +describe.only("test multisig transactions factory", function () { + const config = new TransactionsFactoryConfig({ + chainID: "D", + }); + const factory = new GovernanceTransactionsFactory({ config }); + + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const governanceAddress = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla"; + + it("should create transaction for creating new proposal", function () { + const transaction = factory.createTransactionForNewProposal(alice, { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 1000_000000000000000000n, + }); + + assert.equal(transaction.sender.toBech32(), alice.toBech32()); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 1000_000000000000000000n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 50_192_500n); + assert.equal(transaction.data.toString(), `proposal@${Buffer.from(commitHash).toString("hex")}@0a@0f`); + }); + + it("should create transaction for voting", function () { + const transaction = factory.createTransactionForVoting(alice, { + proposalNonce: 1, + vote: Vote.YES, + }); + + assert.equal(transaction.sender.toBech32(), alice.toBech32()); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 5_171_000n); + assert.equal(transaction.data.toString(), "vote@01@796573"); + }); + + it("should create transaction for closing proposal", function () { + const transaction = factory.createTransactionForClosingProposal(alice, { + proposalNonce: 1, + }); + + assert.equal(transaction.sender.toBech32(), alice.toBech32()); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 50_074_000n); + assert.equal(transaction.data.toString(), "closeProposal@01"); + }); + + it("should create transaction for clearing ended proposals", function () { + const transaction = factory.createTransactionForClearingEndedProposals(alice, { + proposers: [alice, Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")], + }); + + assert.equal(transaction.sender.toBech32(), alice.toBech32()); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 150_273_500n); + assert.equal( + transaction.data.toString(), + "clearEndedProposals@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); + }); + + it("should create transaction for claiming accumulated fees", function () { + const transaction = factory.createTransactionForClaimingAccumulatedFees(alice); + + assert.equal(transaction.sender.toBech32(), alice.toBech32()); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 1_080_000n); + assert.equal(transaction.data.toString(), "claimAccumulatedFees"); + }); + + it("should create transaction for changing config", function () { + const transaction = factory.createTransactionForChangingConfig(alice, { + proposalFee: "1000000000000000000000", + lastProposalFee: "10000000000000000000", + minQuorum: 5000, + minVetoThreshold: 3000, + minPassThreshold: 6000, + }); + + assert.equal(transaction.sender.toBech32(), alice.toBech32()); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 50_219_500n); + assert.equal( + transaction.data.toString(), + "changeConfig@31303030303030303030303030303030303030303030@3130303030303030303030303030303030303030@1388@0bb8@1770", + ); + }); +}); diff --git a/src/governance/governanceTransactionsFactory.ts b/src/governance/governanceTransactionsFactory.ts new file mode 100644 index 000000000..3389df043 --- /dev/null +++ b/src/governance/governanceTransactionsFactory.ts @@ -0,0 +1,133 @@ +import { ArgSerializer, BigUIntValue, StringValue } from "../abi"; +import { Address, Transaction, TransactionsFactoryConfig } from "../core"; +import { GOVERNANCE_CONTRACT_ADDRESS_HEX } from "../core/constants"; +import { TransactionBuilder } from "../core/transactionBuilder"; +import { + ChangeConfigInput, + ClearEndedProposalsInput, + CloseProposalInput, + NewProposalInput, + VoteProposalInput, +} from "./resources"; + +interface IConfig { + chainID: string; + addressHrp: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitForProposal: bigint; + gasLimitForVote: bigint; + gasLimitForClosingProposal: bigint; + gasLimitForClearProposals: bigint; + gasLimitForChangeConfig: bigint; + gasLimitForClaimAccumulatedFees: bigint; +} + +const EXTRA_GAS_LIMIT_FOR_VOTING = 100_000n; + +export class GovernanceTransactionsFactory { + private readonly config: IConfig; + private readonly argSerializer: ArgSerializer; + private readonly governanceContract: Address; + + constructor(options: { config: TransactionsFactoryConfig }) { + this.config = options.config; + this.argSerializer = new ArgSerializer(); + this.governanceContract = Address.newFromHex(GOVERNANCE_CONTRACT_ADDRESS_HEX, this.config.addressHrp); + } + + createTransactionForNewProposal(sender: Address, options: NewProposalInput): Transaction { + const args = [ + new StringValue(options.commitHash), + new BigUIntValue(options.startVoteEpoch), + new BigUIntValue(options.endVoteEpoch), + ]; + const dataParts = ["proposal", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: this.governanceContract, + dataParts: dataParts, + gasLimit: this.config.gasLimitForProposal, + addDataMovementGas: true, + amount: options.nativeTokenAmount, + }).build(); + } + + createTransactionForVoting(sender: Address, options: VoteProposalInput): Transaction { + const args = [new BigUIntValue(options.proposalNonce), new StringValue(options.vote.valueOf())]; + const dataParts = ["vote", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: this.governanceContract, + dataParts: dataParts, + gasLimit: this.config.gasLimitForVote + EXTRA_GAS_LIMIT_FOR_VOTING, + addDataMovementGas: true, + }).build(); + } + + createTransactionForClosingProposal(sender: Address, options: CloseProposalInput): Transaction { + const args = [new BigUIntValue(options.proposalNonce)]; + const dataParts = ["closeProposal", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: this.governanceContract, + dataParts: dataParts, + gasLimit: this.config.gasLimitForClosingProposal, + addDataMovementGas: true, + }).build(); + } + + createTransactionForClearingEndedProposals(sender: Address, options: ClearEndedProposalsInput): Transaction { + const dataParts = ["clearEndedProposals", ...options.proposers.map((address) => address.toHex())]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: this.governanceContract, + dataParts: dataParts, + gasLimit: + this.config.gasLimitForClearProposals + + BigInt(options.proposers.length) * this.config.gasLimitForClearProposals, + addDataMovementGas: true, + }).build(); + } + + createTransactionForClaimingAccumulatedFees(sender: Address): Transaction { + const dataParts = ["claimAccumulatedFees"]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: this.governanceContract, + dataParts: dataParts, + gasLimit: this.config.gasLimitForClaimAccumulatedFees, + addDataMovementGas: true, + }).build(); + } + + createTransactionForChangingConfig(sender: Address, options: ChangeConfigInput): Transaction { + const args = [ + new StringValue(options.proposalFee), + new StringValue(options.lastProposalFee), + new BigUIntValue(options.minQuorum), + new BigUIntValue(options.minVetoThreshold), + new BigUIntValue(options.minPassThreshold), + ]; + const dataParts = ["changeConfig", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: sender, + receiver: this.governanceContract, + dataParts: dataParts, + gasLimit: this.config.gasLimitForChangeConfig, + addDataMovementGas: true, + }).build(); + } +} diff --git a/src/governance/index.ts b/src/governance/index.ts new file mode 100644 index 000000000..ab8af3703 --- /dev/null +++ b/src/governance/index.ts @@ -0,0 +1,2 @@ +export * from "./governanceTransactionsFactory"; +export * from "./resources"; diff --git a/src/governance/resources.ts b/src/governance/resources.ts new file mode 100644 index 000000000..8ad200826 --- /dev/null +++ b/src/governance/resources.ts @@ -0,0 +1,36 @@ +import { Address } from "../core"; + +export type NewProposalInput = { + commitHash: string; + startVoteEpoch: number; + endVoteEpoch: number; + nativeTokenAmount: bigint; +}; + +export type VoteProposalInput = { + proposalNonce: number; + vote: Vote; +}; + +export enum Vote { + YES = "yes", + NO = "no", + ABSTAIN = "abstain", + VETO = "veto", +} + +export type CloseProposalInput = { + proposalNonce: number; +}; + +export type ClearEndedProposalsInput = { + proposers: Address[]; +}; + +export type ChangeConfigInput = { + proposalFee: string; + lastProposalFee: string; + minQuorum: number; + minVetoThreshold: number; + minPassThreshold: number; +}; diff --git a/src/index.ts b/src/index.ts index cb9f585e8..74f3941d6 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 "./governance"; export * from "./multisig"; export * from "./networkProviders"; export * from "./smartContracts"; From fae4e074b65e041c877e76dac694052c7e76bb9b Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 21 May 2025 11:21:31 +0300 Subject: [PATCH 48/54] wip: governance controller --- src/governance/governanceController.spec.ts | 0 src/governance/governanceController.ts | 51 +++++++++++++++++++ ...overnanceTransactionsOutcomeParser.spec.ts | 0 .../governanceTransactionsOutcomeParser.ts | 16 ++++++ src/governance/resources.ts | 7 +++ 5 files changed, 74 insertions(+) create mode 100644 src/governance/governanceController.spec.ts create mode 100644 src/governance/governanceController.ts create mode 100644 src/governance/governanceTransactionsOutcomeParser.spec.ts create mode 100644 src/governance/governanceTransactionsOutcomeParser.ts diff --git a/src/governance/governanceController.spec.ts b/src/governance/governanceController.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/governance/governanceController.ts b/src/governance/governanceController.ts new file mode 100644 index 000000000..453846fdc --- /dev/null +++ b/src/governance/governanceController.ts @@ -0,0 +1,51 @@ +import { + Address, + BaseController, + BaseControllerInput, + IAccount, + Transaction, + TransactionsFactoryConfig, + TransactionWatcher, +} from "../core"; +import { INetworkProvider } from "../networkProviders"; +import { SmartContractController } from "../smartContracts"; +import { GovernanceTransactionsFactory } from "./governanceTransactionsFactory"; +import { NewProposalInput } from "./resources"; + +export class GovernanceController extends BaseController { + private transactionAwaiter: TransactionWatcher; + private governanceFactory: GovernanceTransactionsFactory; + // private multisigParser: MultisigTransactionsOutcomeParser; + private smartContractController: SmartContractController; + + constructor(options: { chainID: string; networkProvider: INetworkProvider }) { + super(); + this.transactionAwaiter = new TransactionWatcher(options.networkProvider); + this.governanceFactory = new GovernanceTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: options.chainID }), + }); + + // this.parser = new MultisigTransactionsOutcomeParser({ abi: options.abi }); + this.smartContractController = new SmartContractController({ + chainID: options.chainID, + networkProvider: options.networkProvider, + }); + } + + async createTransactionForNewProposal( + sender: IAccount, + nonce: bigint, + options: NewProposalInput & BaseControllerInput, + ): Promise { + const transaction = this.governanceFactory.createTransactionForNewProposal(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } +} diff --git a/src/governance/governanceTransactionsOutcomeParser.spec.ts b/src/governance/governanceTransactionsOutcomeParser.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/governance/governanceTransactionsOutcomeParser.ts b/src/governance/governanceTransactionsOutcomeParser.ts new file mode 100644 index 000000000..e5f9eebf6 --- /dev/null +++ b/src/governance/governanceTransactionsOutcomeParser.ts @@ -0,0 +1,16 @@ +import { ArgSerializer } from "../abi"; +import { SmartContractTransactionsOutcomeParser } from "../transactionsOutcomeParsers"; + +export class GovernanceTransactionsOutcomeParser { + private parser: SmartContractTransactionsOutcomeParser; + private addressHrp: string; + private serializer: ArgSerializer; + + constructor(options: { addressHrp: string }) { + this.addressHrp = options.addressHrp; + this.parser = new SmartContractTransactionsOutcomeParser(); + this.serializer = new ArgSerializer(); + } + + parseProposeProposal(transactionOnNetwork: TransactionOnNetwork); +} diff --git a/src/governance/resources.ts b/src/governance/resources.ts index 8ad200826..12104ddcb 100644 --- a/src/governance/resources.ts +++ b/src/governance/resources.ts @@ -34,3 +34,10 @@ export type ChangeConfigInput = { minVetoThreshold: number; minPassThreshold: number; }; + +export type ProposeProposalOutcome = { + proposalNonce: number; + commitHash: string; + startVoteEpoch: number; + endVoteEpoch: number; +}; From a2c51b2d6f336575b2c36c7865aafb7a9e6447e0 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 21 May 2025 11:30:24 +0300 Subject: [PATCH 49/54] fixes after review --- .../governanceTransactionsFactory.spec.ts | 28 ++++++++++--------- .../governanceTransactionsFactory.ts | 10 +++---- src/governance/resources.ts | 4 +-- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/governance/governanceTransactionsFactory.spec.ts b/src/governance/governanceTransactionsFactory.spec.ts index 784aadd3b..c62160c9c 100644 --- a/src/governance/governanceTransactionsFactory.spec.ts +++ b/src/governance/governanceTransactionsFactory.spec.ts @@ -4,7 +4,7 @@ import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; import { GovernanceTransactionsFactory } from "./governanceTransactionsFactory"; import { Vote } from "./resources"; -describe.only("test multisig transactions factory", function () { +describe("test multisig transactions factory", function () { const config = new TransactionsFactoryConfig({ chainID: "D", }); @@ -15,6 +15,8 @@ describe.only("test multisig transactions factory", function () { const governanceAddress = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla"; it("should create transaction for creating new proposal", function () { + const expectedData = `proposal@${Buffer.from(commitHash).toString("hex")}@0a@0f`; + const transaction = factory.createTransactionForNewProposal(alice, { commitHash: commitHash, startVoteEpoch: 10, @@ -27,7 +29,7 @@ describe.only("test multisig transactions factory", function () { assert.equal(transaction.value, 1000_000000000000000000n); assert.equal(transaction.chainID, config.chainID); assert.equal(transaction.gasLimit, 50_192_500n); - assert.equal(transaction.data.toString(), `proposal@${Buffer.from(commitHash).toString("hex")}@0a@0f`); + assert.equal(transaction.data.toString(), expectedData); }); it("should create transaction for voting", function () { @@ -58,6 +60,9 @@ describe.only("test multisig transactions factory", function () { }); it("should create transaction for clearing ended proposals", function () { + const expectedData = + "clearEndedProposals@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; + const transaction = factory.createTransactionForClearingEndedProposals(alice, { proposers: [alice, Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")], }); @@ -67,10 +72,7 @@ describe.only("test multisig transactions factory", function () { assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); assert.equal(transaction.gasLimit, 150_273_500n); - assert.equal( - transaction.data.toString(), - "clearEndedProposals@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", - ); + assert.equal(transaction.data.toString(), expectedData); }); it("should create transaction for claiming accumulated fees", function () { @@ -85,9 +87,12 @@ describe.only("test multisig transactions factory", function () { }); it("should create transaction for changing config", function () { + const expectedData = + "changeConfig@31303030303030303030303030303030303030303030@3130303030303030303030303030303030303030@35303030@33303030@36303030"; + const transaction = factory.createTransactionForChangingConfig(alice, { - proposalFee: "1000000000000000000000", - lastProposalFee: "10000000000000000000", + proposalFee: 1000000000000000000000n, + lastProposalFee: 10000000000000000000n, minQuorum: 5000, minVetoThreshold: 3000, minPassThreshold: 6000, @@ -97,10 +102,7 @@ describe.only("test multisig transactions factory", function () { assert.equal(transaction.receiver.toBech32(), governanceAddress); assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); - assert.equal(transaction.gasLimit, 50_219_500n); - assert.equal( - transaction.data.toString(), - "changeConfig@31303030303030303030303030303030303030303030@3130303030303030303030303030303030303030@1388@0bb8@1770", - ); + assert.equal(transaction.gasLimit, 50_237_500n); + assert.equal(transaction.data.toString(), expectedData); }); }); diff --git a/src/governance/governanceTransactionsFactory.ts b/src/governance/governanceTransactionsFactory.ts index 3389df043..7eb32e2bb 100644 --- a/src/governance/governanceTransactionsFactory.ts +++ b/src/governance/governanceTransactionsFactory.ts @@ -113,11 +113,11 @@ export class GovernanceTransactionsFactory { createTransactionForChangingConfig(sender: Address, options: ChangeConfigInput): Transaction { const args = [ - new StringValue(options.proposalFee), - new StringValue(options.lastProposalFee), - new BigUIntValue(options.minQuorum), - new BigUIntValue(options.minVetoThreshold), - new BigUIntValue(options.minPassThreshold), + new StringValue(options.proposalFee.toString()), + new StringValue(options.lastProposalFee.toString()), + new StringValue(options.minQuorum.toString()), + new StringValue(options.minVetoThreshold.toString()), + new StringValue(options.minPassThreshold.toString()), ]; const dataParts = ["changeConfig", ...this.argSerializer.valuesToStrings(args)]; diff --git a/src/governance/resources.ts b/src/governance/resources.ts index 8ad200826..58094c8f9 100644 --- a/src/governance/resources.ts +++ b/src/governance/resources.ts @@ -28,8 +28,8 @@ export type ClearEndedProposalsInput = { }; export type ChangeConfigInput = { - proposalFee: string; - lastProposalFee: string; + proposalFee: bigint; + lastProposalFee: bigint; minQuorum: number; minVetoThreshold: number; minPassThreshold: number; From 35839745604c1626b611eaa0ede059edd76fa883 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 22 May 2025 09:06:38 +0300 Subject: [PATCH 50/54] governance controller and outcome parser --- src/governance/governanceController.spec.ts | 227 ++++++++++++++++ src/governance/governanceController.ts | 255 +++++++++++++++++- ...overnanceTransactionsOutcomeParser.spec.ts | 94 +++++++ .../governanceTransactionsOutcomeParser.ts | 134 ++++++++- src/governance/index.ts | 2 + src/governance/resources.ts | 53 +++- 6 files changed, 750 insertions(+), 15 deletions(-) diff --git a/src/governance/governanceController.spec.ts b/src/governance/governanceController.spec.ts index e69de29bb..c972655aa 100644 --- a/src/governance/governanceController.spec.ts +++ b/src/governance/governanceController.spec.ts @@ -0,0 +1,227 @@ +import { assert } from "chai"; +import { Account } from "../accounts"; +import { SmartContractQueryResponse } from "../core"; +import { Address } from "../core/address"; +import { ProxyNetworkProvider } from "../networkProviders"; +import { b64TopicsToBytes, MockNetworkProvider } from "../testutils"; +import { KeyPair, UserSecretKey } from "../wallet"; +import { GovernanceController } from "./governanceController"; +import { Vote } from "./resources"; + +describe("test governance controller", function () { + const chainID = "D"; + const controller = new GovernanceController({ + chainID: chainID, + networkProvider: new ProxyNetworkProvider("https://devnet-gateway.multiversx.com"), + }); + + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + const governanceAddress = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla"; + + const aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + const secretKey = UserSecretKey.fromString("413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"); + const keypair = new KeyPair(secretKey); + const alice = Account.newFromKeypair(keypair); + + it("should create transaction for creating new proposal", async function () { + const expectedData = `proposal@${Buffer.from(commitHash).toString("hex")}@0a@0f`; + + const transaction = await controller.createTransactionForNewProposal(alice, alice.getNonceThenIncrement(), { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 1000_000000000000000000n, + }); + + assert.equal(transaction.sender.toBech32(), aliceBech32); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 1000_000000000000000000n); + assert.equal(transaction.chainID, chainID); + assert.equal(transaction.gasLimit, 50_192_500n); + assert.equal(transaction.data.toString(), expectedData); + }); + + it("should create transaction for voting", async function () { + const transaction = await controller.createTransactionForVoting(alice, alice.getNonceThenIncrement(), { + proposalNonce: 1, + vote: Vote.YES, + }); + + assert.equal(transaction.sender.toBech32(), aliceBech32); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, chainID); + assert.equal(transaction.gasLimit, 5_171_000n); + assert.equal(transaction.data.toString(), "vote@01@796573"); + }); + + it("should create transaction for closing proposal", async function () { + const transaction = await controller.createTransactionForClosingProposal(alice, alice.getNonceThenIncrement(), { + proposalNonce: 1, + }); + + assert.equal(transaction.sender.toBech32(), aliceBech32); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, chainID); + assert.equal(transaction.gasLimit, 50_074_000n); + assert.equal(transaction.data.toString(), "closeProposal@01"); + }); + + it("should create transaction for clearing ended proposals", async function () { + const expectedData = + "clearEndedProposals@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; + + const transaction = await controller.createTransactionForClearingEndedProposals( + alice, + alice.getNonceThenIncrement(), + { + proposers: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + }, + ); + + assert.equal(transaction.sender.toBech32(), aliceBech32); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, chainID); + assert.equal(transaction.gasLimit, 150_273_500n); + assert.equal(transaction.data.toString(), expectedData); + }); + + it("should create transaction for claiming accumulated fees", async function () { + const transaction = await controller.createTransactionForClaimingAccumulatedFees( + alice, + alice.getNonceThenIncrement(), + {}, + ); + + assert.equal(transaction.sender.toBech32(), aliceBech32); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, chainID); + assert.equal(transaction.gasLimit, 1_080_000n); + assert.equal(transaction.data.toString(), "claimAccumulatedFees"); + }); + + it("should create transaction for changing config", async function () { + const expectedData = + "changeConfig@31303030303030303030303030303030303030303030@3130303030303030303030303030303030303030@35303030@33303030@36303030"; + + const transaction = await controller.createTransactionForChangingConfig(alice, alice.getNonceThenIncrement(), { + proposalFee: 1000000000000000000000n, + lastProposalFee: 10000000000000000000n, + minQuorum: 5000, + minVetoThreshold: 3000, + minPassThreshold: 6000, + }); + + assert.equal(transaction.sender.toBech32(), aliceBech32); + assert.equal(transaction.receiver.toBech32(), governanceAddress); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, chainID); + assert.equal(transaction.gasLimit, 50_237_500n); + assert.equal(transaction.data.toString(), expectedData); + }); + + it("should get voting power", async function () { + const provider = new MockNetworkProvider(); + const controller = new GovernanceController({ + chainID: chainID, + networkProvider: provider, + }); + + provider.mockQueryContractOnFunction( + "viewVotingPower", + new SmartContractQueryResponse({ + returnDataParts: [Buffer.from("878678326eac900000", "hex")], + returnCode: "ok", + returnMessage: "", + function: "viewVotingPower", + }), + ); + + const votingPower = await controller.getVotingPower(alice.address); + assert.equal(votingPower, 2500_000000000000000000n); + }); + + it("should get config", async function () { + const provider = new MockNetworkProvider(); + const controller = new GovernanceController({ + chainID: chainID, + networkProvider: provider, + }); + + provider.mockQueryContractOnFunction( + "viewConfig", + new SmartContractQueryResponse({ + returnDataParts: [ + Buffer.from("1000000000000000000000"), + Buffer.from("0.2000"), + Buffer.from("0.5000"), + Buffer.from("0.3300"), + Buffer.from("1"), + ], + returnCode: "ok", + returnMessage: "", + function: "viewConfig", + }), + ); + + const config = await controller.getConfig(); + assert.equal(config.proposalFee, 1000_000000000000000000n); + assert.equal(config.minQuorum, 0.2); + assert.equal(config.minPassThreshold, 0.5); + assert.equal(config.minVetoThreshold, 0.33); + assert.equal(config.lastProposalNonce, 1); + }); + + it("should get proposal", async function () { + const provider = new MockNetworkProvider(); + const controller = new GovernanceController({ + chainID: chainID, + networkProvider: provider, + }); + + provider.mockQueryContractOnFunction( + "viewProposal", + new SmartContractQueryResponse({ + returnDataParts: b64TopicsToBytes([ + "NjXJrcXeoAAA", + "MWRiNzM0YzAzMTVmOWVjNDIyYjg4ZjY3OWNjZmUzZTAxOTdiOWQ2Nw==", + "AQ==", + "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", + "NQ==", + "Nw==", + "", + "", + "", + "", + "", + "ZmFsc2U=", + "ZmFsc2U=", + ]), + returnCode: "ok", + returnMessage: "", + function: "viewProposal", + }), + ); + + const proposal = await controller.getProposal(1); + assert.equal(proposal.cost, 1000_000000000000000000n); + assert.equal(proposal.commitHash, "1db734c0315f9ec422b88f679ccfe3e0197b9d67"); + assert.equal(proposal.nonce, 1); + assert.equal(proposal.issuer.toBech32(), aliceBech32); + assert.equal(proposal.startVoteEpoch, 53); + assert.equal(proposal.endVoteEpoch, 55); + assert.equal(proposal.quorumStake, 0n); + assert.equal(proposal.numYesVotes, 0n); + assert.equal(proposal.numNoVotes, 0n); + assert.equal(proposal.numVetoVotes, 0n); + assert.equal(proposal.numAbstainVotes, 0n); + assert.equal(proposal.isClosed, false); + assert.equal(proposal.isPassed, false); + }); +}); diff --git a/src/governance/governanceController.ts b/src/governance/governanceController.ts index 453846fdc..ca4096d42 100644 --- a/src/governance/governanceController.ts +++ b/src/governance/governanceController.ts @@ -1,35 +1,57 @@ +import { AddressType, AddressValue, ArgSerializer, BigUIntType, BigUIntValue, StringType } from "../abi"; import { Address, BaseController, BaseControllerInput, IAccount, + LibraryConfig, Transaction, + TransactionOnNetwork, TransactionsFactoryConfig, TransactionWatcher, } from "../core"; +import { GOVERNANCE_CONTRACT_ADDRESS_HEX } from "../core/constants"; import { INetworkProvider } from "../networkProviders"; import { SmartContractController } from "../smartContracts"; import { GovernanceTransactionsFactory } from "./governanceTransactionsFactory"; -import { NewProposalInput } from "./resources"; +import { GovernanceTransactionsOutcomeParser } from "./governanceTransactionsOutcomeParser"; +import { + ChangeConfigInput, + ClearEndedProposalsInput, + CloseProposalInput, + CloseProposalOutcome, + DelegatedVoteInfo, + GovernanceConfig, + NewProposalInput, + NewProposalOutcome, + ProposalInfo, + VoteOutcome, + VoteProposalInput, +} from "./resources"; export class GovernanceController extends BaseController { - private transactionAwaiter: TransactionWatcher; private governanceFactory: GovernanceTransactionsFactory; - // private multisigParser: MultisigTransactionsOutcomeParser; + private parser: GovernanceTransactionsOutcomeParser; private smartContractController: SmartContractController; + private readonly governanceContract: Address; + private transactionAwaiter: TransactionWatcher; + private addressHrp: string; + private serializer: ArgSerializer; - constructor(options: { chainID: string; networkProvider: INetworkProvider }) { + constructor(options: { chainID: string; networkProvider: INetworkProvider; addressHrp?: string }) { super(); - this.transactionAwaiter = new TransactionWatcher(options.networkProvider); this.governanceFactory = new GovernanceTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), }); - - // this.parser = new MultisigTransactionsOutcomeParser({ abi: options.abi }); this.smartContractController = new SmartContractController({ chainID: options.chainID, networkProvider: options.networkProvider, }); + this.addressHrp = options.addressHrp ?? LibraryConfig.DefaultAddressHrp; + this.parser = new GovernanceTransactionsOutcomeParser({ addressHrp: this.addressHrp }); + this.governanceContract = Address.newFromHex(GOVERNANCE_CONTRACT_ADDRESS_HEX, this.addressHrp); + this.transactionAwaiter = new TransactionWatcher(options.networkProvider); + this.serializer = new ArgSerializer(); } async createTransactionForNewProposal( @@ -48,4 +70,223 @@ export class GovernanceController extends BaseController { return transaction; } + + parseNewProposal(transaction: TransactionOnNetwork): NewProposalOutcome[] { + return this.parser.parseNewProposal(transaction); + } + + async awaitCompletedProposeProposal(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.parseNewProposal(transaction); + } + + async createTransactionForVoting( + sender: IAccount, + nonce: bigint, + options: VoteProposalInput & BaseControllerInput, + ): Promise { + const transaction = this.governanceFactory.createTransactionForVoting(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + parseVote(transaction: TransactionOnNetwork): VoteOutcome[] { + return this.parser.parseVote(transaction); + } + + async awaitCompletedVote(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.parseVote(transaction); + } + + async createTransactionForClosingProposal( + sender: IAccount, + nonce: bigint, + options: CloseProposalInput & BaseControllerInput, + ): Promise { + const transaction = this.governanceFactory.createTransactionForClosingProposal(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + parseCloseProposal(transaction: TransactionOnNetwork): CloseProposalOutcome[] { + return this.parser.parseCloseProposal(transaction); + } + + async awaitCompletedCloseProposal(txHash: string): Promise { + const transaction = await this.transactionAwaiter.awaitCompleted(txHash); + return this.parseCloseProposal(transaction); + } + + async createTransactionForClearingEndedProposals( + sender: IAccount, + nonce: bigint, + options: ClearEndedProposalsInput & BaseControllerInput, + ): Promise { + const transaction = this.governanceFactory.createTransactionForClearingEndedProposals(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForClaimingAccumulatedFees( + sender: IAccount, + nonce: bigint, + options: BaseControllerInput, + ): Promise { + const transaction = this.governanceFactory.createTransactionForClaimingAccumulatedFees(sender.address); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForChangingConfig( + sender: IAccount, + nonce: bigint, + options: ChangeConfigInput & BaseControllerInput, + ): Promise { + const transaction = this.governanceFactory.createTransactionForChangingConfig(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async getVotingPower(address: Address): Promise { + const result = await this.smartContractController.query({ + contract: this.governanceContract, + function: "viewVotingPower", + arguments: [new AddressValue(address)], + }); + + const votingPower = { type: new BigUIntType() }; + const data = this.serializer.buffersToValues(result, [votingPower]); + return BigInt(data[0].valueOf().toFixed()); + } + + async getConfig(): Promise { + const result = await this.smartContractController.query({ + contract: this.governanceContract, + function: "viewConfig", + arguments: [], + }); + + const proposalFee = BigInt(Buffer.from(result[0]).toString()); + const minQuorum = Number(Buffer.from(result[1]).toString()); + const minPassThreshold = Number(Buffer.from(result[2]).toString()); + const minVetoThreshold = Number(Buffer.from(result[3]).toString()); + const lastProposalNonce = Number(Buffer.from(result[4]).toString()); + + return { + proposalFee, + minQuorum, + minPassThreshold, + minVetoThreshold, + lastProposalNonce, + }; + } + + async getProposal(proposalNonce: number): Promise { + const result = await this.smartContractController.query({ + contract: this.governanceContract, + function: "viewProposal", + arguments: [new BigUIntValue(proposalNonce)], + }); + + const proposalCost = { type: new BigUIntType() }; + const commitHash = { type: new StringType() }; + const nonce = { type: new BigUIntType() }; + const issuer = { type: new AddressType() }; + const startVoteEpoch = { type: new BigUIntType() }; + const endVoteEpoch = { type: new BigUIntType() }; + const quorumStake = { type: new BigUIntType() }; + const numVotesYes = { type: new BigUIntType() }; + const numVotesNo = { type: new BigUIntType() }; + const numVotesVeto = { type: new BigUIntType() }; + const numVotesAbstain = { type: new BigUIntType() }; + + const data = this.serializer.buffersToValues(result.slice(0, 11), [ + proposalCost, + commitHash, + nonce, + issuer, + startVoteEpoch, + endVoteEpoch, + quorumStake, + numVotesYes, + numVotesNo, + numVotesVeto, + numVotesAbstain, + ]); + + const isClosed = Buffer.from(result[11]).toString() === "true"; + const isPassed = Buffer.from(result[12]).toString() === "true"; + + return { + cost: BigInt(data[0].valueOf().toFixed()), + commitHash: data[1].valueOf(), + nonce: Number(data[2].valueOf().toString()), + issuer: data[3].valueOf(), + startVoteEpoch: Number(data[4].valueOf().toString()), + endVoteEpoch: Number(data[5].valueOf().toString()), + quorumStake: BigInt(data[6].valueOf().toFixed()), + numYesVotes: BigInt(data[7].valueOf().toFixed()), + numNoVotes: BigInt(data[8].valueOf().toFixed()), + numVetoVotes: BigInt(data[9].valueOf().toFixed()), + numAbstainVotes: BigInt(data[10].valueOf().toFixed()), + isClosed: isClosed, + isPassed: isPassed, + }; + } + + async getDelegatedVoteInfo(): Promise { + const result = await this.smartContractController.query({ + contract: this.governanceContract, + function: "viewDelegatedVoteInfo", + arguments: [], + }); + + const usedStake = BigInt(Buffer.from(result[0]).toString()); + const usedPower = BigInt(Buffer.from(result[1]).toString()); + const totalStake = BigInt(Buffer.from(result[2]).toString()); + const totalPower = BigInt(Buffer.from(result[3]).toString()); + + return { + usedStake, + usedPower, + totalStake, + totalPower, + }; + } } diff --git a/src/governance/governanceTransactionsOutcomeParser.spec.ts b/src/governance/governanceTransactionsOutcomeParser.spec.ts index e69de29bb..de29b6c06 100644 --- a/src/governance/governanceTransactionsOutcomeParser.spec.ts +++ b/src/governance/governanceTransactionsOutcomeParser.spec.ts @@ -0,0 +1,94 @@ +import { assert } from "chai"; +import { TransactionEvent, TransactionLogs, TransactionOnNetwork } from "../core"; +import { Address } from "../core/address"; +import { b64TopicsToBytes } from "../testutils"; +import { GovernanceTransactionsOutcomeParser } from "./governanceTransactionsOutcomeParser"; + +describe("test multisig transactions outcome parser", function () { + const parser = new GovernanceTransactionsOutcomeParser({}); + + it("should parse transaction for creating new proposal", function () { + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const proposalEvent = new TransactionEvent({ + address: Address.empty(), + identifier: "proposal", + topics: [new Uint8Array([0x01]), Buffer.from(commitHash), Buffer.from("5"), Buffer.from("7")], + }); + const logs = new TransactionLogs({ events: [proposalEvent] }); + + const transaction = new TransactionOnNetwork({ logs: logs }); + + const outcome = parser.parseNewProposal(transaction); + assert.equal(outcome.length, 1); + assert.equal(outcome[0].proposalNonce, 1); + assert.equal(outcome[0].commitHash, commitHash); + assert.equal(outcome[0].startVoteEpoch, 53); + assert.equal(outcome[0].endVoteEpoch, 55); + }); + + it("should parse transaction for voting", function () { + const encodedTopics = ["AQ==", "eWVz", "BlpNol0wFsAAAA==", "BlpNol0wFsAAAA=="]; + + const voteEvent = new TransactionEvent({ + address: Address.empty(), + identifier: "vote", + topics: b64TopicsToBytes(encodedTopics), + }); + const logs = new TransactionLogs({ events: [voteEvent] }); + + const transaction = new TransactionOnNetwork({ logs: logs }); + + const outcome = parser.parseVote(transaction); + assert.equal(outcome.length, 1); + assert.equal(outcome[0].proposalNonce, 1); + assert.equal(outcome[0].vote, "yes"); + assert.equal(outcome[0].totalStake, 30000_000000000000000000n); + assert.equal(outcome[0].votingPower, 30000_000000000000000000n); + }); + + it("should parse transaction for delegating vote", function () { + const encodedTopics = [ + "AQ==", + "YWJzdGFpbg==", + "a3Qc0P1f8raaWzOVkcJbHHxHOx2+LI6S8CM9aV+W6KY=", + "Ah4Z4Mm6skAAAA==", + "Ah4Z4Mm6skAAAA==", + ]; + + const voteEvent = new TransactionEvent({ + address: Address.empty(), + identifier: "delegateVote", + topics: b64TopicsToBytes(encodedTopics), + }); + const logs = new TransactionLogs({ events: [voteEvent] }); + + const transaction = new TransactionOnNetwork({ logs: logs }); + + const outcome = parser.parseDelegateVote(transaction); + assert.equal(outcome.length, 1); + assert.equal(outcome[0].proposalNonce, 1); + assert.equal(outcome[0].vote, "abstain"); + assert.equal(outcome[0].voter.toBech32(), "erd1dd6pe58atletdxjmxw2ersjmr37ywwcahckgayhsyv7kjhukaznqx2mzqf"); + assert.equal(outcome[0].userStake, 10000_000000000000000000n); + assert.equal(outcome[0].votingPower, 10000_000000000000000000n); + }); + + it("should parse transaction for closing proposal", function () { + const encodedTopics = ["ZDVkMjRhYTY1ZWY5OWM3NDcxMjkxMmZkOGJiMmE1MDVjY2RmMDYyYw==", "dHJ1ZQ=="]; + + const voteEvent = new TransactionEvent({ + address: Address.empty(), + identifier: "closeProposal", + topics: b64TopicsToBytes(encodedTopics), + }); + const logs = new TransactionLogs({ events: [voteEvent] }); + + const transaction = new TransactionOnNetwork({ logs: logs }); + + const outcome = parser.parseCloseProposal(transaction); + assert.equal(outcome.length, 1); + assert.equal(outcome[0].commitHash, "d5d24aa65ef99c74712912fd8bb2a505ccdf062c"); + assert.equal(outcome[0].passed, true); + }); +}); diff --git a/src/governance/governanceTransactionsOutcomeParser.ts b/src/governance/governanceTransactionsOutcomeParser.ts index e5f9eebf6..9ceea7b7d 100644 --- a/src/governance/governanceTransactionsOutcomeParser.ts +++ b/src/governance/governanceTransactionsOutcomeParser.ts @@ -1,16 +1,136 @@ -import { ArgSerializer } from "../abi"; -import { SmartContractTransactionsOutcomeParser } from "../transactionsOutcomeParsers"; +import { AddressType, ArgSerializer, BigUIntType, StringType } from "../abi"; +import { Address, ErrParseTransactionOutcome, LibraryConfig, TransactionEvent, TransactionOnNetwork } from "../core"; +import { findEventsByIdentifier } from "../transactionsOutcomeParsers"; +import { CloseProposalOutcome, DelegateVoteOutcome, NewProposalOutcome, VoteOutcome } from "./resources"; export class GovernanceTransactionsOutcomeParser { - private parser: SmartContractTransactionsOutcomeParser; private addressHrp: string; private serializer: ArgSerializer; - constructor(options: { addressHrp: string }) { - this.addressHrp = options.addressHrp; - this.parser = new SmartContractTransactionsOutcomeParser(); + constructor(options: { addressHrp?: string }) { + this.addressHrp = options.addressHrp ?? LibraryConfig.DefaultAddressHrp; this.serializer = new ArgSerializer(); } - parseProposeProposal(transactionOnNetwork: TransactionOnNetwork); + parseNewProposal(transactionOnNetwork: TransactionOnNetwork): NewProposalOutcome[] { + this.ensureNoError(transactionOnNetwork.logs.events); + + const events = findEventsByIdentifier(transactionOnNetwork, "proposal"); + + const proposalNonce = { type: new BigUIntType() }; + const commitHash = { type: new StringType() }; + const startVoteEpoch = { type: new BigUIntType() }; + const endVoteEpoch = { type: new BigUIntType() }; + + let outcome: NewProposalOutcome[] = []; + for (const event of events) { + const data = this.serializer.buffersToValues( + event.topics.map((topic) => Buffer.from(topic)), + [proposalNonce, commitHash, startVoteEpoch, endVoteEpoch], + ); + + outcome.push({ + proposalNonce: data[0].valueOf(), + commitHash: data[1].valueOf(), + startVoteEpoch: data[2].valueOf(), + endVoteEpoch: data[3].valueOf(), + }); + } + + return outcome; + } + + parseVote(transactionOnNetwork: TransactionOnNetwork): VoteOutcome[] { + this.ensureNoError(transactionOnNetwork.logs.events); + + const events = findEventsByIdentifier(transactionOnNetwork, "vote"); + + const proposalToVote = { type: new BigUIntType() }; + const vote = { type: new StringType() }; + const totalStake = { type: new BigUIntType() }; + const votingPower = { type: new BigUIntType() }; + + let outcome: VoteOutcome[] = []; + for (const event of events) { + const data = this.serializer.buffersToValues( + event.topics.map((topic) => Buffer.from(topic)), + [proposalToVote, vote, totalStake, votingPower], + ); + + outcome.push({ + proposalNonce: Number(data[0].toString()), + vote: data[1].valueOf(), + totalStake: BigInt(data[2].valueOf().toFixed()), + votingPower: BigInt(data[3].valueOf().toFixed()), + }); + } + + return outcome; + } + + parseDelegateVote(transactionOnNetwork: TransactionOnNetwork): DelegateVoteOutcome[] { + this.ensureNoError(transactionOnNetwork.logs.events); + + const events = findEventsByIdentifier(transactionOnNetwork, "delegateVote"); + + const proposalToVote = { type: new BigUIntType() }; + const vote = { type: new StringType() }; + const voter = { type: new AddressType() }; + const userStake = { type: new BigUIntType() }; + const votingPower = { type: new BigUIntType() }; + + let outcome: DelegateVoteOutcome[] = []; + for (const event of events) { + const data = this.serializer.buffersToValues( + event.topics.map((topic) => Buffer.from(topic)), + [proposalToVote, vote, voter, userStake, votingPower], + ); + + outcome.push({ + proposalNonce: Number(data[0].toString()), + vote: data[1].valueOf(), + voter: new Address(data[2].valueOf().getPublicKey(), this.addressHrp), + userStake: BigInt(data[3].valueOf().toFixed()), + votingPower: BigInt(data[4].valueOf().toFixed()), + }); + } + + return outcome; + } + + parseCloseProposal(transactionOnNetwork: TransactionOnNetwork): CloseProposalOutcome[] { + this.ensureNoError(transactionOnNetwork.logs.events); + + const events = findEventsByIdentifier(transactionOnNetwork, "closeProposal"); + + let outcome: CloseProposalOutcome[] = []; + for (const event of events) { + const commitHash = Buffer.from(event.topics[0]).toString(); + const passed = Buffer.from(event.topics[1]).toString() === "true"; + + outcome.push({ + commitHash: commitHash, + passed: passed, + }); + } + + return outcome; + } + + private ensureNoError(transactionEvents: TransactionEvent[]) { + for (const event of transactionEvents) { + if (event.identifier == "signalError") { + const data = Buffer.from(event.additionalData[0]?.toString().slice(1)).toString() || ""; + const message = this.decodeTopicAsString(event.topics[1]); + + throw new ErrParseTransactionOutcome( + `encountered signalError: ${message} (${Buffer.from(data, "hex").toString()})`, + ); + } + } + } + + private decodeTopicAsString(topic: Uint8Array): string { + return Buffer.from(topic).toString(); + } } diff --git a/src/governance/index.ts b/src/governance/index.ts index ab8af3703..8fd19e1ab 100644 --- a/src/governance/index.ts +++ b/src/governance/index.ts @@ -1,2 +1,4 @@ +export * from "./governanceController"; export * from "./governanceTransactionsFactory"; +export * from "./governanceTransactionsOutcomeParser"; export * from "./resources"; diff --git a/src/governance/resources.ts b/src/governance/resources.ts index 3c7392d5d..34c4440d1 100644 --- a/src/governance/resources.ts +++ b/src/governance/resources.ts @@ -35,9 +35,60 @@ export type ChangeConfigInput = { minPassThreshold: number; }; -export type ProposeProposalOutcome = { +export type NewProposalOutcome = { proposalNonce: number; commitHash: string; startVoteEpoch: number; endVoteEpoch: number; }; + +export type VoteOutcome = { + proposalNonce: number; + vote: string; + totalStake: bigint; + votingPower: bigint; +}; + +export type DelegateVoteOutcome = { + proposalNonce: number; + vote: string; + voter: Address; + userStake: bigint; + votingPower: bigint; +}; + +export type CloseProposalOutcome = { + commitHash: string; + passed: boolean; +}; + +export type GovernanceConfig = { + proposalFee: bigint; + minQuorum: number; + minPassThreshold: number; + minVetoThreshold: number; + lastProposalNonce: number; +}; + +export type ProposalInfo = { + cost: bigint; + commitHash: string; + nonce: number; + issuer: Address; + startVoteEpoch: number; + endVoteEpoch: number; + quorumStake: bigint; + numYesVotes: bigint; + numNoVotes: bigint; + numVetoVotes: bigint; + numAbstainVotes: bigint; + isClosed: boolean; + isPassed: boolean; +}; + +export type DelegatedVoteInfo = { + usedStake: bigint; + usedPower: bigint; + totalStake: bigint; + totalPower: bigint; +}; From 5d48c326796b67c505957aa4dd5220f11c70f374 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 22 May 2025 14:06:41 +0300 Subject: [PATCH 51/54] fix test description --- src/governance/governanceTransactionsFactory.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/governance/governanceTransactionsFactory.spec.ts b/src/governance/governanceTransactionsFactory.spec.ts index c62160c9c..bdc9911fc 100644 --- a/src/governance/governanceTransactionsFactory.spec.ts +++ b/src/governance/governanceTransactionsFactory.spec.ts @@ -4,7 +4,7 @@ import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; import { GovernanceTransactionsFactory } from "./governanceTransactionsFactory"; import { Vote } from "./resources"; -describe("test multisig transactions factory", function () { +describe("test governance transactions factory", function () { const config = new TransactionsFactoryConfig({ chainID: "D", }); From 9f40f824b55efba51b070be70db2f9ad986c2cc3 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 26 May 2025 13:43:49 +0300 Subject: [PATCH 52/54] fixes after review --- src/governance/governanceController.ts | 12 ++++++------ .../governanceTransactionsOutcomeParser.ts | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/governance/governanceController.ts b/src/governance/governanceController.ts index ca4096d42..c6eb02f74 100644 --- a/src/governance/governanceController.ts +++ b/src/governance/governanceController.ts @@ -30,13 +30,13 @@ import { } from "./resources"; export class GovernanceController extends BaseController { - private governanceFactory: GovernanceTransactionsFactory; - private parser: GovernanceTransactionsOutcomeParser; - private smartContractController: SmartContractController; + private readonly governanceFactory: GovernanceTransactionsFactory; + private readonly parser: GovernanceTransactionsOutcomeParser; + private readonly smartContractController: SmartContractController; private readonly governanceContract: Address; - private transactionAwaiter: TransactionWatcher; - private addressHrp: string; - private serializer: ArgSerializer; + private readonly transactionAwaiter: TransactionWatcher; + private readonly addressHrp: string; + private readonly serializer: ArgSerializer; constructor(options: { chainID: string; networkProvider: INetworkProvider; addressHrp?: string }) { super(); diff --git a/src/governance/governanceTransactionsOutcomeParser.ts b/src/governance/governanceTransactionsOutcomeParser.ts index 9ceea7b7d..0bea641dd 100644 --- a/src/governance/governanceTransactionsOutcomeParser.ts +++ b/src/governance/governanceTransactionsOutcomeParser.ts @@ -22,7 +22,7 @@ export class GovernanceTransactionsOutcomeParser { const startVoteEpoch = { type: new BigUIntType() }; const endVoteEpoch = { type: new BigUIntType() }; - let outcome: NewProposalOutcome[] = []; + const outcome: NewProposalOutcome[] = []; for (const event of events) { const data = this.serializer.buffersToValues( event.topics.map((topic) => Buffer.from(topic)), @@ -50,7 +50,7 @@ export class GovernanceTransactionsOutcomeParser { const totalStake = { type: new BigUIntType() }; const votingPower = { type: new BigUIntType() }; - let outcome: VoteOutcome[] = []; + const outcome: VoteOutcome[] = []; for (const event of events) { const data = this.serializer.buffersToValues( event.topics.map((topic) => Buffer.from(topic)), @@ -79,7 +79,7 @@ export class GovernanceTransactionsOutcomeParser { const userStake = { type: new BigUIntType() }; const votingPower = { type: new BigUIntType() }; - let outcome: DelegateVoteOutcome[] = []; + const outcome: DelegateVoteOutcome[] = []; for (const event of events) { const data = this.serializer.buffersToValues( event.topics.map((topic) => Buffer.from(topic)), @@ -103,7 +103,7 @@ export class GovernanceTransactionsOutcomeParser { const events = findEventsByIdentifier(transactionOnNetwork, "closeProposal"); - let outcome: CloseProposalOutcome[] = []; + const outcome: CloseProposalOutcome[] = []; for (const event of events) { const commitHash = Buffer.from(event.topics[0]).toString(); const passed = Buffer.from(event.topics[1]).toString() === "true"; From b7867ce7914124e74773388491496e369745d5bc Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 27 May 2025 17:07:17 +0300 Subject: [PATCH 53/54] fix BigNumber to BigInt conversion --- package-lock.json | 4 ++-- package.json | 2 +- src/multisig/multisigController.spec.ts | 2 +- src/multisig/resources.ts | 14 +++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f358a9fc..23ecd26b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.2.0", + "version": "14.2.0-beta.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.2.0", + "version": "14.2.0-beta.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 5a2df1f1d..3a8ac002b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.2.0", + "version": "14.2.0-beta.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", diff --git a/src/multisig/multisigController.spec.ts b/src/multisig/multisigController.spec.ts index 501678a93..addcb1b6f 100644 --- a/src/multisig/multisigController.spec.ts +++ b/src/multisig/multisigController.spec.ts @@ -304,7 +304,7 @@ describe("test multisig controller query methods", () => { const mappedRes = result as resources.SendTransferExecuteEgld; assert.equal(mappedRes.receiver.toBech32(), "erd1qqqqqqqqqqqqqpgq6qr0w0zzyysklfneh32eqp2cf383zc89d8sstnkl60"); - assert.equal(mappedRes.funcionName, "add"); + assert.equal(mappedRes.functionName, "add"); assert.equal(mappedRes.amount, 42n); }); diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index 2b600af36..2e3b0c872 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -175,16 +175,16 @@ export class SendTransferExecuteEgld extends MultisigAction { receiver: Address; amount: bigint; optionalGasLimit?: bigint; - funcionName: string; + functionName: string; arguments: Uint8Array[]; constructor(data: any) { super(); this.type = MultisigActionEnum.SendTransferExecuteEgld; this.receiver = data.to; - this.amount = BigInt(data.egld_amount?.toString() ?? 0); - this.optionalGasLimit = BigInt(data.opt_gas_limit?.toString() ?? 0); - this.funcionName = data.endpoint_name.toString(); + this.amount = BigInt(data.egld_amount?.toFixed() ?? 0); + this.optionalGasLimit = BigInt(data.opt_gas_limit?.toFixed() ?? 0); + this.functionName = data.endpoint_name.toString(); this.arguments = data.arguments; } } @@ -206,7 +206,7 @@ export class SendTransferExecuteEsdt extends MultisigAction { amount: token.amount, }), ); - this.optionalGasLimit = BigInt(data.opt_gas_limit.toString()); + this.optionalGasLimit = BigInt(data.opt_gas_limit.toFixed()); this.funcionName = Buffer.from(data.endpoint_name.toString(), "hex").toString(); this.arguments = data.arguments; @@ -224,8 +224,8 @@ export class SendAsyncCall extends MultisigAction { super(); this.type = MultisigActionEnum.SendAsyncCall; this.receiver = data.to; - this.amount = BigInt(data.egld_amount?.toString() ?? 0); - this.optionalGasLimit = BigInt(data.opt_gas_limit?.toString() ?? 0); + this.amount = BigInt(data.egld_amount?.toFixed() ?? 0); + this.optionalGasLimit = BigInt(data.opt_gas_limit?.toFixed() ?? 0); this.funcionName = data.endpoint_name.toString(); this.arguments = data.arguments; } From 1aefad04ca9346159c61143c0fa1ddecae34aec8 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 4 Jun 2025 17:09:04 +0300 Subject: [PATCH 54/54] 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 23ecd26b6..1f358a9fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.2.0-beta.0", + "version": "14.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.2.0-beta.0", + "version": "14.2.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 3a8ac002b..5a2df1f1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.2.0-beta.0", + "version": "14.2.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com",