diff --git a/cookbook/accountManagement.ts b/cookbook/accountManagement.ts index cac55396c..39b84bd1d 100644 --- a/cookbook/accountManagement.ts +++ b/cookbook/accountManagement.ts @@ -160,7 +160,7 @@ import { Account, Address, DevnetEntrypoint } from "../src"; // md-ignore const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); const alice = await Account.newFromPem(filePath); - const transaction = await factory.createTransactionForUnguardingAccount(alice.address); + const transaction = await factory.createTransactionForUnguardingAccount(alice.address, {}); // fetch the nonce of the network // md-as-comment alice.nonce = await entrypoint.recallAccountNonce(alice.address); diff --git a/cookbook/cookbook.md b/cookbook/cookbook.md index ac991db31..1d9126277 100644 --- a/cookbook/cookbook.md +++ b/cookbook/cookbook.md @@ -17,7 +17,7 @@ const entrypoint = new DevnetEntrypoint(); If you'd like to connect to a third-party API, you can specify the url parameter: ```js -const apiEntrypoint = new DevnetEntrypoint("https://custom-multiversx-devnet-api.com"); +const apiEntrypoint = new DevnetEntrypoint({ url: "https://custom-multiversx-devnet-api.com" }); ``` #### Using a Proxy @@ -25,7 +25,7 @@ const apiEntrypoint = new DevnetEntrypoint("https://custom-multiversx-devnet-api By default, the DevnetEntrypoint uses the standard API. However, you can create a custom entrypoint that interacts with a proxy by specifying the kind parameter: ```js -const customEntrypoint = new DevnetEntrypoint("https://devnet-gateway.multiversx.com", "proxy"); +const customEntrypoint = new DevnetEntrypoint({ url: "https://devnet-gateway.multiversx.com", kind: "proxy" }); ``` ## Creating Accounts @@ -634,6 +634,26 @@ There are two ways to create controllers and factories: } ``` +### Estimating the Gas Limit for Transactions +Additionally, when creating transaction factories or controllers, we can pass an additional argument, a **gas limit estimator**. +This gas estimator simulates the transaction before being sent and computes the `gasLimit` that it will require. +The `GasLimitEstimator` can be initialized with a multiplier, so that the estimated value will be multiplied by the specified value. +The gas limit estimator can be provided to any factory or controller available. Let's see how we can create a `GasLimitEstimator` and use it. + +```js +{ + const api = new ApiNetworkProvider("https://devnet-api.multiversx.com"); + let gasEstimator = new GasLimitEstimator({ networkProvider: api }); // create a gas limit estimator with default multiplier of 1.0 + let gasEstimatorWithMultiplier = new GasLimitEstimator({ networkProvider: api, gasMultiplier: 1.5 }); // create a gas limit estimator with a multiplier of 1.5 + + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const transfersFactory = new TransferTransactionsFactory({ + config: config, + gasLimitEstimator: gasEstimatorWithMultiplier, // or `gasEstimator` + }); +} +``` + ### Token transfers We can send both native tokens (EGLD) and ESDT tokens using either the controller or the factory. #### Native Token Transfers Using the Controller @@ -683,7 +703,7 @@ You will need to handle these aspects after the transaction is created. const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - const transaction = factory.createTransactionForTransfer(alice.address, { + const transaction = await factory.createTransactionForTransfer(alice.address, { receiver: bob, nativeAmount: 1000000000000000000n, }); @@ -762,7 +782,7 @@ When using the factory, only the sender's address is required. As a result, the const sft = new Token({ identifier: "SFT-987654", nonce: 10n }); const thirdTransfer = new TokenTransfer({ token: sft, amount: 7n }); // for SFTs we set the desired amount we want to send - const transaction = factory.createTransactionForTransfer(alice.address, { + const transaction = await factory.createTransactionForTransfer(alice.address, { receiver: bob, tokenTransfers: [firstTransfer, secondTransfer, thirdTransfer], }); @@ -825,9 +845,8 @@ While interactions with the contract are possible without the ABI, they are much #### Loading the ABI from a file ```js { - let abiJson = await promises.readFile("../src/testData/adder.abi.json", { encoding: "utf8" }); - let abiObj = JSON.parse(abiJson); - let abi = Abi.create(abiObj); + let abiJson = await fs.promises.readFile("../src/testdata/adder.abi.json", { encoding: "utf8" }); + const abi = Abi.create(JSON.parse(abiJson)); } ``` @@ -896,9 +915,12 @@ This allows arguments to be passed as native Javascript values. If the ABI is no sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the contract bytecode - const bytecode = await promises.readFile("../src/testData/adder.wasm"); + const bytecode = await fs.promises.readFile("../src/testdata/adder.wasm"); // load the abi file - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const controller = entrypoint.createSmartContractController(abi); @@ -934,8 +956,10 @@ let args = [new U32Value(42), "hello", { foo: "bar" }, new TokenIdentifierValue( ```js { // We use the transaction hash we got when broadcasting the transaction - - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const entrypoint = new DevnetEntrypoint(); const controller = entrypoint.createSmartContractController(abi); const outcome = await controller.awaitCompletedDeploy("txHash"); // waits for transaction completion and parses the result @@ -968,7 +992,7 @@ Even before broadcasting, at the moment you know the sender's address and the no { const entrypoint = new DevnetEntrypoint(); const factory = entrypoint.createSmartContractTransactionsFactory(); - const bytecode = await promises.readFile("../contracts/adder.wasm"); + const bytecode = await fs.promises.readFile("../contracts/adder.wasm"); // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: let args: any[] = [new BigUIntValue(42)]; @@ -977,7 +1001,7 @@ Even before broadcasting, at the moment you know the sender's address and the no const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); const alice = await Account.newFromPem(filePath); - const deployTransaction = factory.createTransactionForDeploy(alice.address, { + const deployTransaction = await factory.createTransactionForDeploy(alice.address, { bytecode: bytecode, gasLimit: 6000000n, arguments: args, @@ -1001,7 +1025,7 @@ After the transaction is created the nonce needs to be properly set and the tran const factory = entrypoint.createSmartContractTransactionsFactory(); // load the contract bytecode - const bytecode = await promises.readFile("../src/testData/adder.wasm"); + const bytecode = await fs.promises.readFile("../src/testdata/adder.wasm"); // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: let args: any[] = [new BigUIntValue(42)]; @@ -1057,7 +1081,10 @@ In this section we'll see how we can call an endpoint of our previously deployed sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the abi file - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const controller = entrypoint.createSmartContractController(abi); const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); @@ -1109,7 +1136,10 @@ Both EGLD and ESDT tokens or a combination of both can be sent. This functionali sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the abi file - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // get the smart contracts controller const controller = entrypoint.createSmartContractController(abi); @@ -1199,7 +1229,10 @@ As said before, the `add` endpoint we called does not return anything, but we co { // load the abi file const entrypoint = new DevnetEntrypoint(); - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const parser = new SmartContractTransactionsOutcomeParser({ abi }); const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; const transactionOnNetwork = await entrypoint.getTransaction(txHash); @@ -1218,7 +1251,10 @@ First, we load the abi file, then we fetch the transaction, we extract the event { // load the abi files const entrypoint = new DevnetEntrypoint(); - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const parser = new TransactionEventsParser({ abi }); const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; const transactionOnNetwork = await entrypoint.getTransaction(txHash); @@ -1233,7 +1269,10 @@ Whenever needed, the contract ABI can be used for manually encoding or decoding Let's encode a struct called EsdtTokenPayment (of [multisig](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig) contract) into binary data. ```js { - const abi = await loadAbiRegistry("../src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const paymentType = abi.getStruct("EsdtTokenPayment"); const codec = new BinaryCodec(); @@ -1252,7 +1291,10 @@ Let's encode a struct called EsdtTokenPayment (of [multisig](https://github.com/ Now let's decode a struct using the ABI. ```js { - const abi = await loadAbiRegistry("../src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const actionStructType = abi.getEnum("Action"); const data = Buffer.from( "0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107", @@ -1275,7 +1317,10 @@ In this example, we will query the **adder smart contract** by calling its `getS { const entrypoint = new DevnetEntrypoint(); const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the controller const controller = entrypoint.createSmartContractController(abi); @@ -1293,7 +1338,10 @@ This approach achieves the same result as the previous example. const entrypoint = new DevnetEntrypoint(); // load the abi - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // the contract address we'll query const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); @@ -1326,13 +1374,16 @@ However, in this case, the contract address is already known. Like deploying a s sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the abi - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the controller const controller = entrypoint.createSmartContractController(abi); // load the contract bytecode; this is the new contract code, the one we want to upgrade to - const bytecode = await promises.readFile("../src/testData/adder.wasm"); + const bytecode = await fs.promises.readFile("../src/testdata/adder.wasm"); // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: let args: any[] = [new U32Value(42)]; @@ -1910,7 +1961,7 @@ Once a guardian is set, we must wait **20 epochs** before it can be activated. A const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); const alice = await Account.newFromPem(filePath); - const transaction = await factory.createTransactionForUnguardingAccount(alice.address); + const transaction = await factory.createTransactionForUnguardingAccount(alice.address, {}); // fetch the nonce of the network alice.nonce = await entrypoint.recallAccountNonce(alice.address); @@ -2626,8 +2677,11 @@ These operations can be performed using both the **controller** and the **factor #### Deploying a Multisig Smart Contract using the controller ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); - const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); + const bytecode = await fs.promises.readFile("src/testdata/multisig-full.wasm"); // create the entrypoint and the multisig controller const entrypoint = new DevnetEntrypoint(); @@ -2662,8 +2716,11 @@ These operations can be performed using both the **controller** and the **factor #### Deploying a Multisig Smart Contract using the factory ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); - const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); + const bytecode = await fs.promises.readFile("src/testdata/multisig-full.wasm"); // create the entrypoint and the multisig factory const entrypoint = new DevnetEntrypoint(); @@ -2698,9 +2755,12 @@ The id can be used later for signing and performing the proposal. ```js { + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the entrypoint and the multisig controller const entrypoint = new DevnetEntrypoint(); - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); const controller = entrypoint.createMultisigController(abi); const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); @@ -2735,7 +2795,10 @@ Proposing an action for a multisig contract using the MultisigFactory is very si ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the entrypoint and the multisig factory const entrypoint = new DevnetEntrypoint(); @@ -2781,7 +2844,10 @@ Let's query the contract to get all board members. ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the entrypoint and the multisig controller const entrypoint = new DevnetEntrypoint(); @@ -2847,7 +2913,7 @@ These operations can be performed using both the **controller** and the **factor const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; - const transaction = factory.createTransactionForNewProposal(alice.address, { + const transaction = await factory.createTransactionForNewProposal(alice.address, { commitHash: commitHash, startVoteEpoch: 10, endVoteEpoch: 15, @@ -2918,7 +2984,7 @@ These operations can be performed using both the **controller** and the **factor const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); const alice = await Account.newFromPem(filePath); - const transaction = factory.createTransactionForVoting(alice.address, { + const transaction = await factory.createTransactionForVoting(alice.address, { proposalNonce: 1, vote: Vote.YES, }); diff --git a/cookbook/entrypoints.ts b/cookbook/entrypoints.ts index d486df254..50077125a 100644 --- a/cookbook/entrypoints.ts +++ b/cookbook/entrypoints.ts @@ -20,7 +20,7 @@ import { DevnetEntrypoint } from "../src"; // md-ignore // If you'd like to connect to a third-party API, you can specify the url parameter: // ```js - const apiEntrypoint = new DevnetEntrypoint("https://custom-multiversx-devnet-api.com"); + const apiEntrypoint = new DevnetEntrypoint({ url: "https://custom-multiversx-devnet-api.com" }); // ``` // #### Using a Proxy @@ -28,7 +28,7 @@ import { DevnetEntrypoint } from "../src"; // md-ignore // By default, the DevnetEntrypoint uses the standard API. However, you can create a custom entrypoint that interacts with a proxy by specifying the kind parameter: // ```js - const customEntrypoint = new DevnetEntrypoint("https://devnet-gateway.multiversx.com", "proxy"); + const customEntrypoint = new DevnetEntrypoint({ url: "https://devnet-gateway.multiversx.com", kind: "proxy" }); // ``` })().catch((e) => { console.log({ e }); diff --git a/cookbook/generate.py b/cookbook/generate.py index 7bab31ce7..567cc49a6 100644 --- a/cookbook/generate.py +++ b/cookbook/generate.py @@ -1,6 +1,6 @@ import re from pathlib import Path -from typing import Dict, List +from typing import List current_dir = Path(__file__).parent.absolute() @@ -32,7 +32,7 @@ # we don't want this ceremonial piece of code to show up in the rendered cookbook TO_REMOVE = [ - """(async () => {""", + """(async () => {""", """})().catch((e) => { console.log({ e }); });"""] @@ -59,12 +59,12 @@ def main(): def render_file(input_file: Path) -> List[str]: input_text = input_file.read_text() - for item in TO_REMOVE: + for item in TO_REMOVE: input_text = input_text.replace(item, "") input_lines = input_text.splitlines() start = input_lines.index(DIRECTIVE_START) - + input_lines = input_lines[start:] output_lines: List[str] = [] @@ -86,9 +86,9 @@ def render_file(input_file: Path) -> List[str]: if is_comment and not should_keep_as_comment: line = line.strip().strip("/").strip() - else: + else: if line.startswith(TO_UNINDENT_SPACE): - line = line[len(TO_UNINDENT_SPACE):] + line = line[len(TO_UNINDENT_SPACE):] diff --git a/cookbook/governance.ts b/cookbook/governance.ts index ec836d280..08e844a95 100644 --- a/cookbook/governance.ts +++ b/cookbook/governance.ts @@ -56,7 +56,7 @@ import { Account, DevnetEntrypoint, GovernanceTransactionsOutcomeParser, Vote } const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; - const transaction = factory.createTransactionForNewProposal(alice.address, { + const transaction = await factory.createTransactionForNewProposal(alice.address, { commitHash: commitHash, startVoteEpoch: 10, endVoteEpoch: 15, @@ -127,7 +127,7 @@ import { Account, DevnetEntrypoint, GovernanceTransactionsOutcomeParser, Vote } const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); const alice = await Account.newFromPem(filePath); - const transaction = factory.createTransactionForVoting(alice.address, { + const transaction = await factory.createTransactionForVoting(alice.address, { proposalNonce: 1, vote: Vote.YES, }); diff --git a/cookbook/multisig.ts b/cookbook/multisig.ts index c76ef8734..f552c3882 100644 --- a/cookbook/multisig.ts +++ b/cookbook/multisig.ts @@ -1,7 +1,6 @@ +import * as fs from "fs"; // md-ignore import path from "path"; // md-ignore -import { Account, Address, DevnetEntrypoint, TransactionWatcher } from "../src"; // md-ignore -import { MultisigTransactionsOutcomeParser } from "../src/multisig/multisigTransactionsOutcomeParser"; -import { loadAbiRegistry, loadContractCode } from "../src/testutils"; +import { Abi, Account, Address, DevnetEntrypoint, MultisigTransactionsOutcomeParser, TransactionWatcher } from "../src"; // md-ignore // md-start (async () => { // ### Multisig @@ -17,8 +16,11 @@ import { loadAbiRegistry, loadContractCode } from "../src/testutils"; // #### Deploying a Multisig Smart Contract using the controller // ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); - const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); + const bytecode = await fs.promises.readFile("src/testdata/multisig-full.wasm"); // create the entrypoint and the multisig controller // md-as-comment const entrypoint = new DevnetEntrypoint(); @@ -53,8 +55,11 @@ import { loadAbiRegistry, loadContractCode } from "../src/testutils"; // #### Deploying a Multisig Smart Contract using the factory // ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); - const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); + const bytecode = await fs.promises.readFile("src/testdata/multisig-full.wasm"); // create the entrypoint and the multisig factory // md-as-comment const entrypoint = new DevnetEntrypoint(); @@ -89,9 +94,12 @@ import { loadAbiRegistry, loadContractCode } from "../src/testutils"; // ```js { + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the entrypoint and the multisig controller // md-as-comment const entrypoint = new DevnetEntrypoint(); - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); const controller = entrypoint.createMultisigController(abi); const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); @@ -126,7 +134,10 @@ import { loadAbiRegistry, loadContractCode } from "../src/testutils"; // ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the entrypoint and the multisig factory // md-as-comment const entrypoint = new DevnetEntrypoint(); @@ -172,7 +183,10 @@ import { loadAbiRegistry, loadContractCode } from "../src/testutils"; // ```js { - const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the entrypoint and the multisig controller // md-as-comment const entrypoint = new DevnetEntrypoint(); diff --git a/cookbook/smartContracts.ts b/cookbook/smartContracts.ts index 6fb5dbde9..afe9bafb4 100644 --- a/cookbook/smartContracts.ts +++ b/cookbook/smartContracts.ts @@ -1,5 +1,5 @@ import axios from "axios"; // md-ignore -import { promises } from "fs"; // md-ignore +import * as fs from "fs"; // md-ignore import path from "path"; // md-ignore import { Abi, @@ -20,7 +20,6 @@ import { U32Value, U64Value, } from "../src"; // md-ignore -import { loadAbiRegistry } from "../src/testutils"; // md-start (async () => { // ### Smart Contracts @@ -33,9 +32,8 @@ import { loadAbiRegistry } from "../src/testutils"; // #### Loading the ABI from a file // ```js { - let abiJson = await promises.readFile("../src/testData/adder.abi.json", { encoding: "utf8" }); - let abiObj = JSON.parse(abiJson); - let abi = Abi.create(abiObj); + let abiJson = await fs.promises.readFile("../src/testdata/adder.abi.json", { encoding: "utf8" }); + const abi = Abi.create(JSON.parse(abiJson)); } // ``` @@ -104,9 +102,12 @@ import { loadAbiRegistry } from "../src/testutils"; sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the contract bytecode - const bytecode = await promises.readFile("../src/testData/adder.wasm"); + const bytecode = await fs.promises.readFile("../src/testdata/adder.wasm"); // load the abi file - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const controller = entrypoint.createSmartContractController(abi); @@ -142,8 +143,10 @@ import { loadAbiRegistry } from "../src/testutils"; // ```js { // We use the transaction hash we got when broadcasting the transaction - - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const entrypoint = new DevnetEntrypoint(); const controller = entrypoint.createSmartContractController(abi); const outcome = await controller.awaitCompletedDeploy("txHash"); // waits for transaction completion and parses the result @@ -176,7 +179,7 @@ import { loadAbiRegistry } from "../src/testutils"; { const entrypoint = new DevnetEntrypoint(); const factory = entrypoint.createSmartContractTransactionsFactory(); - const bytecode = await promises.readFile("../contracts/adder.wasm"); + const bytecode = await fs.promises.readFile("../contracts/adder.wasm"); // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: // md-as-comment let args: any[] = [new BigUIntValue(42)]; @@ -185,7 +188,7 @@ import { loadAbiRegistry } from "../src/testutils"; const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); const alice = await Account.newFromPem(filePath); - const deployTransaction = factory.createTransactionForDeploy(alice.address, { + const deployTransaction = await factory.createTransactionForDeploy(alice.address, { bytecode: bytecode, gasLimit: 6000000n, arguments: args, @@ -209,7 +212,7 @@ import { loadAbiRegistry } from "../src/testutils"; const factory = entrypoint.createSmartContractTransactionsFactory(); // load the contract bytecode - const bytecode = await promises.readFile("../src/testData/adder.wasm"); + const bytecode = await fs.promises.readFile("../src/testdata/adder.wasm"); // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: // md-as-comment let args: any[] = [new BigUIntValue(42)]; @@ -265,7 +268,10 @@ import { loadAbiRegistry } from "../src/testutils"; sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the abi file - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const controller = entrypoint.createSmartContractController(abi); const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); @@ -317,7 +323,10 @@ import { loadAbiRegistry } from "../src/testutils"; sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the abi file - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // get the smart contracts controller const controller = entrypoint.createSmartContractController(abi); @@ -407,7 +416,10 @@ import { loadAbiRegistry } from "../src/testutils"; { // load the abi file const entrypoint = new DevnetEntrypoint(); - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const parser = new SmartContractTransactionsOutcomeParser({ abi }); const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; const transactionOnNetwork = await entrypoint.getTransaction(txHash); @@ -426,7 +438,10 @@ import { loadAbiRegistry } from "../src/testutils"; { // load the abi files const entrypoint = new DevnetEntrypoint(); - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const parser = new TransactionEventsParser({ abi }); const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; const transactionOnNetwork = await entrypoint.getTransaction(txHash); @@ -441,7 +456,10 @@ import { loadAbiRegistry } from "../src/testutils"; // Let's encode a struct called EsdtTokenPayment (of [multisig](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig) contract) into binary data. // ```js { - const abi = await loadAbiRegistry("../src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const paymentType = abi.getStruct("EsdtTokenPayment"); const codec = new BinaryCodec(); @@ -460,7 +478,10 @@ import { loadAbiRegistry } from "../src/testutils"; // Now let's decode a struct using the ABI. // ```js { - const abi = await loadAbiRegistry("../src/testdata/multisig-full.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/multisig-full.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); const actionStructType = abi.getEnum("Action"); const data = Buffer.from( "0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107", @@ -483,7 +504,10 @@ import { loadAbiRegistry } from "../src/testutils"; { const entrypoint = new DevnetEntrypoint(); const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the controller const controller = entrypoint.createSmartContractController(abi); @@ -501,7 +525,10 @@ import { loadAbiRegistry } from "../src/testutils"; const entrypoint = new DevnetEntrypoint(); // load the abi - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // the contract address we'll query const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); @@ -534,13 +561,16 @@ import { loadAbiRegistry } from "../src/testutils"; sender.nonce = await entrypoint.recallAccountNonce(sender.address); // load the abi - const abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const jsonContent: string = await fs.promises.readFile("../src/testdata/adder.abi.json", { + encoding: "utf8", + }); + const abi = Abi.create(JSON.parse(jsonContent)); // create the controller const controller = entrypoint.createSmartContractController(abi); // load the contract bytecode; this is the new contract code, the one we want to upgrade to - const bytecode = await promises.readFile("../src/testData/adder.wasm"); + const bytecode = await fs.promises.readFile("../src/testdata/adder.wasm"); // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: // md-as-comment let args: any[] = [new U32Value(42)]; diff --git a/cookbook/transactions.ts b/cookbook/transactions.ts index 169ba5a64..b2d822812 100644 --- a/cookbook/transactions.ts +++ b/cookbook/transactions.ts @@ -2,7 +2,9 @@ import path from "path"; // md-ignore import { Account, Address, + ApiNetworkProvider, DevnetEntrypoint, + GasLimitEstimator, Token, TokenTransfer, TransactionsFactoryConfig, @@ -41,6 +43,26 @@ import { } // ``` + // ### Estimating the Gas Limit for Transactions + // Additionally, when creating transaction factories or controllers, we can pass an additional argument, a **gas limit estimator**. + // This gas estimator simulates the transaction before being sent and computes the `gasLimit` that it will require. + // The `GasLimitEstimator` can be initialized with a multiplier, so that the estimated value will be multiplied by the specified value. + // The gas limit estimator can be provided to any factory or controller available. Let's see how we can create a `GasLimitEstimator` and use it. + + // ```js + { + const api = new ApiNetworkProvider("https://devnet-api.multiversx.com"); + let gasEstimator = new GasLimitEstimator({ networkProvider: api }); // create a gas limit estimator with default multiplier of 1.0 + let gasEstimatorWithMultiplier = new GasLimitEstimator({ networkProvider: api, gasMultiplier: 1.5 }); // create a gas limit estimator with a multiplier of 1.5 + + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const transfersFactory = new TransferTransactionsFactory({ + config: config, + gasLimitEstimator: gasEstimatorWithMultiplier, // or `gasEstimator` + }); + } + // ``` + // ### Token transfers // We can send both native tokens (EGLD) and ESDT tokens using either the controller or the factory. // #### Native Token Transfers Using the Controller @@ -90,7 +112,7 @@ import { const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - const transaction = factory.createTransactionForTransfer(alice.address, { + const transaction = await factory.createTransactionForTransfer(alice.address, { receiver: bob, nativeAmount: 1000000000000000000n, }); @@ -169,7 +191,7 @@ import { const sft = new Token({ identifier: "SFT-987654", nonce: 10n }); const thirdTransfer = new TokenTransfer({ token: sft, amount: 7n }); // for SFTs we set the desired amount we want to send - const transaction = factory.createTransactionForTransfer(alice.address, { + const transaction = await factory.createTransactionForTransfer(alice.address, { receiver: bob, tokenTransfers: [firstTransfer, secondTransfer, thirdTransfer], }); diff --git a/package-lock.json b/package-lock.json index 90590dc9f..2b5112cf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "14.2.9", + "version": "15.0.0-beta.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "14.2.9", + "version": "15.0.0-beta.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index a93e045d2..8b365aa6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "14.2.9", + "version": "15.0.0-beta.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", diff --git a/src/abi/code.spec.ts b/src/abi/code.spec.ts deleted file mode 100644 index 1933cfefe..000000000 --- a/src/abi/code.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { assert } from "chai"; -import { Code } from "./code"; - -describe("Code Class Tests", function () { - const sampleHex = "abcdef0123456789"; - const sampleBuffer = Buffer.from(sampleHex, "hex"); - - it("should create Code from buffer", function () { - const code = Code.fromBuffer(sampleBuffer); - - assert.instanceOf(code, Code); - assert.equal(code.toString(), sampleHex); - }); - - it("should create Code from hex string", function () { - const code = Code.fromHex(sampleHex); - - assert.instanceOf(code, Code); - assert.equal(code.toString(), sampleHex); - }); - - it("should return the correct buffer from valueOf", function () { - const code = Code.fromHex(sampleHex); - const buffer = code.valueOf(); - - assert.isTrue(Buffer.isBuffer(buffer)); - assert.equal(buffer.toString("hex"), sampleHex); - }); - - it("should compute hash correctly", function () { - const code = Code.fromHex(sampleHex); - const hash = code.computeHash(); - - assert.instanceOf(hash, Buffer); - assert.equal(hash.toString("hex"), "ac86b78afd9bdda3641a47a4aff2a7ee26acd40cc534d63655e9dfbf3f890a02"); - }); -}); diff --git a/src/abi/code.ts b/src/abi/code.ts deleted file mode 100644 index dd3946052..000000000 --- a/src/abi/code.ts +++ /dev/null @@ -1,45 +0,0 @@ -const createHasher = require("blake2b"); -const CODE_HASH_LENGTH = 32; - -/** - * * @deprecated Use the bytecode directly - * Bytecode of a Smart Contract, as an abstraction. - */ -export class Code { - private readonly hex: string; - - private constructor(hex: string) { - this.hex = hex; - } - - /** - * Creates a Code object from a buffer (sequence of bytes). - */ - static fromBuffer(code: Buffer): Code { - return new Code(code.toString("hex")); - } - - /** - * Creates a Code object from a hex-encoded string. - */ - static fromHex(hex: string): Code { - return new Code(hex); - } - - /** - * Returns the bytecode as a hex-encoded string. - */ - toString(): string { - return this.hex; - } - - valueOf(): Buffer { - return Buffer.from(this.hex, "hex"); - } - - computeHash(): Buffer { - const hash = createHasher(CODE_HASH_LENGTH).update(this.valueOf()).digest(); - - return Buffer.from(hash); - } -} diff --git a/src/abi/index.ts b/src/abi/index.ts index e72a5528f..415a34caf 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -1,12 +1,9 @@ export * from "./argSerializer"; export * from "./argumentErrorContext"; -export * from "./code"; export * from "./codec"; export * from "./function"; -export * from "./interaction"; export * from "./interface"; export * from "./nativeSerializer"; export * from "./query"; export * from "./returnCode"; -export * from "./smartContract"; export * from "./typesystem"; diff --git a/src/abi/interaction.local.net.spec.ts b/src/abi/interaction.local.net.spec.ts deleted file mode 100644 index f5c8f8066..000000000 --- a/src/abi/interaction.local.net.spec.ts +++ /dev/null @@ -1,448 +0,0 @@ -import BigNumber from "bignumber.js"; -import { assert } from "chai"; -import { promises } from "fs"; -import { Account } from "../accounts"; -import { Transaction } from "../core/transaction"; -import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; -import { TransactionWatcher } from "../core/transactionWatcher"; -import { - SmartContractController, - SmartContractTransactionsFactory, - SmartContractTransactionsOutcomeParser, -} from "../smartContracts"; -import { loadAbiRegistry, prepareDeployment } from "../testutils"; -import { createLocalnetProvider } from "../testutils/networkProviders"; -import { getTestWalletsPath } from "../testutils/utils"; -import { Interaction } from "./interaction"; -import { SmartContract } from "./smartContract"; -import { ManagedDecimalValue } from "./typesystem"; -describe("test smart contract interactor", function () { - let provider = createLocalnetProvider(); - let alice: Account; - - before(async function () { - alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); - }); - - it("should interact with 'answer' (local testnet) using the SmartContractTransactionsFactory", async function () { - this.timeout(80000); - - let abi = await loadAbiRegistry("src/testdata/answer.abi.json"); - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ - config: config, - abi: abi, - }); - - const bytecode = await promises.readFile("src/testdata/answer.wasm"); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 3000000n, - }); - deployTransaction.nonce = alice.nonce; - - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - - const transactionCompletionAwaiter = new TransactionWatcher({ - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash); - }, - }); - - const deployTxHash = await provider.sendTransaction(deployTransaction); - alice.incrementNonce(); - - const queryController = new SmartContractController({ - chainID: "localnet", - networkProvider: provider, - abi: abi, - }); - let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); - let response = queryController.parseDeploy(transactionOnNetwork); - assert.isTrue(response.returnCode == "ok"); - - const query = queryController.createQuery({ - contract: contractAddress, - caller: alice.address, - function: "getUltimateAnswer", - arguments: [], - }); - - const queryResponse = await queryController.runQuery(query); - const parsed = queryController.parseQueryResponse(queryResponse); - assert.lengthOf(parsed, 1); - assert.deepEqual(parsed[0], new BigNumber(42)); - - // Query - let transaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "getUltimateAnswer", - gasLimit: 3000000n, - }); - transaction.nonce = alice.getNonceThenIncrement(); - transaction.signature = await alice.signTransaction(transaction); - - await provider.sendTransaction(transaction); - - // Execute, and wait for execution - transaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "getUltimateAnswer", - gasLimit: 3000000n, - }); - transaction.nonce = alice.getNonceThenIncrement(); - transaction.signature = await alice.signTransaction(transaction); - - const executeTxHash = await provider.sendTransaction(transaction); - transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(executeTxHash); - const executeResponse = queryController.parseExecute(transactionOnNetwork); - - assert.isTrue(executeResponse.values.length == 1); - assert.deepEqual(executeResponse.values[0], new BigNumber(42)); - assert.isTrue(executeResponse.returnCode == "ok"); - }); - - it("should interact with 'basic-features' (local testnet)", async function () { - this.timeout(140000); - - let abi = await loadAbiRegistry("src/testdata/basic-features.abi.json"); - let contract = new SmartContract({ abi: abi }); - let controller = new SmartContractController({ - chainID: "localnet", - networkProvider: provider, - abi: abi, - }); - - let network = await provider.getNetworkConfig(); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - // Deploy the contract - let deployTransaction = await prepareDeployment({ - contract: contract, - deployer: alice, - codePath: "src/testdata/basic-features.wasm", - gasLimit: 600000000n, - initArguments: [], - chainID: network.chainID, - }); - let deployTxHash = await provider.sendTransaction(deployTransaction); - let deployResponse = await controller.awaitCompletedDeploy(deployTxHash); - assert.isTrue(deployResponse.returnCode == "ok"); - - let returnEgldInteraction = ( - contract.methods - .returns_egld_decimal([]) - .withGasLimit(10000000n) - .withChainID(network.chainID) - .withSender(alice.address) - .withValue(1n) - ); - - // returnEgld() - let returnEgldTransaction = returnEgldInteraction - .withSender(alice.address) - .useThenIncrementNonceOf(alice) - .buildTransaction(); - - let additionInteraction = contract.methods - .managed_decimal_addition([new ManagedDecimalValue("2.5", 2), new ManagedDecimalValue("2.7", 2)]) - .withGasLimit(10000000n) - .withChainID(network.chainID) - .withSender(alice.address) - .withValue(0n); - - // addition() - let additionTransaction = additionInteraction - .withSender(alice.address) - .useThenIncrementNonceOf(alice) - .buildTransaction(); - - // log - let mdLnInteraction = contract.methods - .managed_decimal_ln([new ManagedDecimalValue("23", 9)]) - .withGasLimit(10000000n) - .withChainID(network.chainID) - .withSender(alice.address) - .withValue(0n); - - // mdLn() - let mdLnTransaction = mdLnInteraction - .withSender(alice.address) - .useThenIncrementNonceOf(alice) - .buildTransaction(); - - let additionVarInteraction = contract.methods - .managed_decimal_addition_var([ - new ManagedDecimalValue("4", 2, true), - new ManagedDecimalValue("5", 2, true), - ]) - .withGasLimit(50000000n) - .withChainID(network.chainID) - .withSender(alice.address) - .withValue(0n); - - // addition() - let additionVarTransaction = additionVarInteraction - .withSender(alice.address) - .useThenIncrementNonceOf(alice) - .buildTransaction(); - - let lnVarInteraction = contract.methods - .managed_decimal_ln_var([new ManagedDecimalValue("23", 9, true)]) - .withGasLimit(50000000n) - .withChainID(network.chainID) - .withSender(alice.address) - .withValue(0n); - - // managed_decimal_ln_var() - let lnVarTransaction = lnVarInteraction - .withSender(alice.address) - .useThenIncrementNonceOf(alice) - .buildTransaction(); - - // returnEgld() - await signTransaction({ transaction: returnEgldTransaction, wallet: alice }); - let txHash = await provider.sendTransaction(returnEgldTransaction); - let response = await controller.awaitCompletedExecute(txHash); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - assert.deepEqual(response.values[0], new BigNumber("0.000000000000000001")); - - // addition with const decimals() - await signTransaction({ transaction: additionTransaction, wallet: alice }); - txHash = await provider.sendTransaction(additionTransaction); - response = await controller.awaitCompletedExecute(txHash); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - assert.deepEqual(response.values[0], new BigNumber("5.2")); - - // log - await signTransaction({ transaction: mdLnTransaction, wallet: alice }); - txHash = await provider.sendTransaction(mdLnTransaction); - response = await controller.awaitCompletedExecute(txHash); - - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - assert.deepEqual(response.values[0], new BigNumber("3.135553845")); - - // addition with var decimals - await signTransaction({ transaction: additionVarTransaction, wallet: alice }); - txHash = await provider.sendTransaction(additionVarTransaction); - response = await controller.awaitCompletedExecute(txHash); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - assert.deepEqual(response.values[0], new BigNumber("9")); - - // log - await signTransaction({ transaction: lnVarTransaction, wallet: alice }); - txHash = await provider.sendTransaction(lnVarTransaction); - response = await controller.awaitCompletedExecute(txHash); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - assert.deepEqual(response.values[0], new BigNumber("3.135553845")); - }); - - it("should interact with 'counter' (local testnet) using the SmartContractTransactionsFactory", async function () { - this.timeout(120000); - - let abi = await loadAbiRegistry("src/testdata/counter.abi.json"); - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ - config: config, - abi: abi, - }); - const parser = new SmartContractTransactionsOutcomeParser({ abi: abi }); - - const bytecode = await promises.readFile("src/testdata/counter.wasm"); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 3000000n, - }); - deployTransaction.nonce = alice.nonce; - - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - - const transactionCompletionAwaiter = new TransactionWatcher({ - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash); - }, - }); - - const deployTxHash = await provider.sendTransaction(deployTransaction); - alice.incrementNonce(); - let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); - const deployResponse = parser.parseDeploy({ transactionOnNetwork }); - assert.isTrue(deployResponse.returnCode == "ok"); - - const queryController = new SmartContractController({ - chainID: "localnet", - networkProvider: provider, - abi: abi, - }); - - let incrementTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "increment", - gasLimit: 3000000n, - }); - incrementTransaction.nonce = alice.getNonceThenIncrement(); - - incrementTransaction.signature = await alice.signTransaction(incrementTransaction); - - // Query "get()" - const query = queryController.createQuery({ - contract: contractAddress, - function: "get", - arguments: [], - }); - const queryResponse = await queryController.runQuery(query); - const parsed = queryController.parseQueryResponse(queryResponse); - assert.deepEqual(parsed[0], new BigNumber(1)); - - const incrementTxHash = await provider.sendTransaction(incrementTransaction); - transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(incrementTxHash); - - let response = parser.parseExecute({ transactionOnNetwork }); - assert.deepEqual(response.values[0], new BigNumber(2)); - - let decrementTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "decrement", - gasLimit: 3000000n, - }); - decrementTransaction.nonce = alice.getNonceThenIncrement(); - decrementTransaction.signature = await alice.signTransaction(decrementTransaction); - - await provider.sendTransaction(decrementTransaction); - - decrementTransaction.nonce = alice.nonce; - decrementTransaction.signature = await alice.signTransaction(decrementTransaction); - - const decrementTxHash = await provider.sendTransaction(decrementTransaction); - transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(decrementTxHash); - response = parser.parseExecute({ transactionOnNetwork }); - }); - - it("should interact with 'lottery-esdt' (local testnet) using the SmartContractTransactionsFactory", async function () { - this.timeout(140000); - - let abi = await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"); - let parser = new SmartContractTransactionsOutcomeParser({ abi: abi }); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ - config: config, - abi: abi, - }); - - const bytecode = await promises.readFile("src/testdata/lottery-esdt.wasm"); - - // Deploy the contract - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 100000000n, - }); - deployTransaction.nonce = alice.nonce; - - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - - const transactionCompletionAwaiter = new TransactionWatcher({ - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash); - }, - }); - - const deployTxHash = await provider.sendTransaction(deployTransaction); - alice.incrementNonce(); - let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); - const deployResponse = parser.parseDeploy({ transactionOnNetwork }); - assert.isTrue(deployResponse.returnCode == "ok"); - - // start() - let startTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "start", - arguments: ["lucky", "EGLD", 1, null, null, 1, null, null], - gasLimit: 30000000n, - }); - startTransaction.nonce = alice.getNonceThenIncrement(); - startTransaction.signature = await alice.signTransaction(startTransaction); - - const startTxHash = await provider.sendTransaction(startTransaction); - transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(startTxHash); - let response = parser.parseExecute({ transactionOnNetwork }); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 0); - - // status() - let lotteryStatusTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "status", - arguments: ["lucky"], - gasLimit: 5000000n, - }); - lotteryStatusTransaction.nonce = alice.getNonceThenIncrement(); - lotteryStatusTransaction.signature = await alice.signTransaction(lotteryStatusTransaction); - - const statusTxHash = await provider.sendTransaction(lotteryStatusTransaction); - transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(statusTxHash); - response = parser.parseExecute({ transactionOnNetwork }); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - assert.equal(response.values[0].name, "Running"); - - // getlotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let lotteryInfoTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "getLotteryInfo", - arguments: ["lucky"], - gasLimit: 5000000n, - }); - lotteryInfoTransaction.nonce = alice.getNonceThenIncrement(); - lotteryInfoTransaction.signature = await alice.signTransaction(lotteryInfoTransaction); - - const infoTxHash = await provider.sendTransaction(lotteryInfoTransaction); - transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(infoTxHash); - response = parser.parseExecute({ transactionOnNetwork }); - assert.isTrue(response.returnCode == "ok"); - assert.lengthOf(response.values, 1); - - // Ignore "deadline" field in our test - let info = response.values[0]!.valueOf(); - delete info.deadline; - - assert.deepEqual(info, { - token_identifier: "EGLD", - ticket_price: new BigNumber("1"), - tickets_left: new BigNumber(800), - max_entries_per_user: new BigNumber(1), - prize_distribution: Buffer.from([0x64]), - prize_pool: new BigNumber("0"), - }); - }); - - async function signTransaction(options: { transaction: Transaction; wallet: Account }) { - const transaction = options.transaction; - const wallet = options.wallet; - - transaction.signature = await wallet.signTransaction(transaction); - } -}); diff --git a/src/abi/interaction.spec.ts b/src/abi/interaction.spec.ts deleted file mode 100644 index b7ed0a261..000000000 --- a/src/abi/interaction.spec.ts +++ /dev/null @@ -1,428 +0,0 @@ -import BigNumber from "bignumber.js"; -import { assert } from "chai"; -import { Account } from "../accounts"; -import { Address } from "../core/address"; -import { SmartContractQueryResponse } from "../core/smartContractQuery"; -import { Token, TokenTransfer } from "../core/tokens"; -import { Transaction } from "../core/transaction"; -import { SmartContractController } from "../smartContracts"; -import { loadAbiRegistry, MockNetworkProvider } from "../testutils"; -import { getTestWalletsPath } from "../testutils/utils"; -import { ContractFunction } from "./function"; -import { Interaction } from "./interaction"; -import { SmartContract } from "./smartContract"; -import { BigUIntValue, BytesValue, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; - -describe("test smart contract interactor", function () { - let dummyAddress = new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); - let provider = new MockNetworkProvider(); - let alice: Account; - - before(async function () { - alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); - }); - - it("should set transaction fields", async function () { - let contract = new SmartContract({ address: dummyAddress }); - let dummyFunction = new ContractFunction("dummy"); - let interaction = new Interaction(contract, dummyFunction, []); - - let transaction = interaction - .withSender(alice.address) - .withNonce(7n) - .withValue(TokenTransfer.newFromNativeAmount(1000000000000000000n).amount) - .withGasLimit(20000000n) - .buildTransaction(); - - assert.deepEqual(transaction.receiver, dummyAddress); - assert.equal(transaction.value.toString(), "1000000000000000000"); - assert.equal(transaction.nonce, 7n); - assert.equal(transaction.gasLimit, 20000000n); - }); - - it("should set transfers (payments) on contract calls (transfer and execute)", async function () { - let contract = new SmartContract({ address: dummyAddress }); - let dummyFunction = new ContractFunction("dummy"); - let alice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - - const TokenFoo = (amount: BigNumber.Value) => - new TokenTransfer({ token: new Token({ identifier: "FOO-6ce17b" }), amount: BigInt(amount.toString()) }); - const TokenBar = (amount: BigNumber.Value) => - new TokenTransfer({ token: new Token({ identifier: "BAR-5bc08f" }), amount: BigInt(amount.toString()) }); - const LKMEX = (nonce: number, amount: BigNumber.Value) => - new TokenTransfer({ - token: new Token({ identifier: "LKMEX-aab910", nonce: BigInt(nonce) }), - amount: BigInt(amount.toString()), - }); - const nonFungibleToken = (nonce: number) => - new TokenTransfer({ token: new Token({ identifier: "MOS-b9b4b2", nonce: BigInt(nonce) }), amount: 1n }); - - const hexFoo = "464f4f2d366365313762"; - const hexBar = "4241522d356263303866"; - const hexLKMEX = "4c4b4d45582d616162393130"; - const hexNFT = "4d4f532d623962346232"; - const hexContractAddress = contract.getAddress().toHex(); - const hexDummyFunction = "64756d6d79"; - - // ESDT, single - let transaction = new Interaction(contract, dummyFunction, []) - .withSender(alice) - .withSingleESDTTransfer(TokenFoo(10)) - .buildTransaction(); - - assert.equal(transaction.data.toString(), `ESDTTransfer@${hexFoo}@0a@${hexDummyFunction}`); - - // Meta ESDT (special SFT), single - transaction = new Interaction(contract, dummyFunction, []) - .withSender(alice) - .withSingleESDTNFTTransfer(LKMEX(123456, "123456000000000000000")) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`, - ); - - // Meta ESDT (special SFT), single, but using "withSender()" (recommended) - transaction = new Interaction(contract, dummyFunction, []) - .withSingleESDTNFTTransfer(LKMEX(123456, 123456000000000000000)) - .withSender(alice) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`, - ); - - // NFT, single - transaction = new Interaction(contract, dummyFunction, []) - .withSender(alice) - .withSingleESDTNFTTransfer(nonFungibleToken(1)) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `ESDTNFTTransfer@${hexNFT}@01@01@${hexContractAddress}@${hexDummyFunction}`, - ); - - // NFT, single, but using "withSender()" (recommended) - transaction = new Interaction(contract, dummyFunction, []) - .withSingleESDTNFTTransfer(nonFungibleToken(1)) - .withSender(alice) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `ESDTNFTTransfer@${hexNFT}@01@01@${hexContractAddress}@${hexDummyFunction}`, - ); - - // ESDT, multiple - transaction = new Interaction(contract, dummyFunction, []) - .withSender(alice) - .withMultiESDTNFTTransfer([TokenFoo(3), TokenBar(3140)]) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`, - ); - - // ESDT, multiple, but using "withSender()" (recommended) - transaction = new Interaction(contract, dummyFunction, []) - .withMultiESDTNFTTransfer([TokenFoo(3), TokenBar(3140)]) - .withSender(alice) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`, - ); - - // NFT, multiple - transaction = new Interaction(contract, dummyFunction, []) - .withSender(alice) - .withMultiESDTNFTTransfer([nonFungibleToken(1), nonFungibleToken(42)]) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - assert.equal( - transaction.data.toString(), - `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexNFT}@01@01@${hexNFT}@2a@01@${hexDummyFunction}`, - ); - - // NFT, multiple, but using "withSender()" (recommended) - transaction = new Interaction(contract, dummyFunction, []) - .withMultiESDTNFTTransfer([nonFungibleToken(1), nonFungibleToken(42)]) - .withSender(alice) - .buildTransaction(); - - assert.equal(transaction.sender.toBech32(), alice.toBech32()); - assert.equal(transaction.receiver.toBech32(), alice.toBech32()); - }); - - it("should create transaction, with ABI, with transfer & execute", async function () { - const abi = await loadAbiRegistry("src/testdata/answer.abi.json"); - const contract = new SmartContract({ address: dummyAddress, abi: abi }); - const alice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const token = new Token({ identifier: "FOO-abcdef", nonce: 0n }); - - const transaction = contract.methods - .getUltimateAnswer() - .withChainID("T") - .withSender(alice) - .withGasLimit(543210n) - .withSingleESDTTransfer(new TokenTransfer({ token, amount: 100n })) - .withNonce(42n) - .buildTransaction(); - - assert.deepEqual( - transaction, - new Transaction({ - chainID: "T", - sender: alice, - receiver: dummyAddress, - data: Buffer.from("ESDTTransfer@464f4f2d616263646566@64@676574556c74696d617465416e73776572"), - gasLimit: 543210n, - value: 0n, - version: 2, - nonce: 42n, - }), - ); - }); - - it("should interact with 'answer'", async function () { - this.timeout(30000); - let abi = await loadAbiRegistry("src/testdata/answer.abi.json"); - let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let controller = new SmartContractController({ chainID: "D", networkProvider: provider, abi: abi }); - - let interaction = contract.methods.getUltimateAnswer().withGasLimit(543210n).withChainID("T"); - - assert.equal(contract.getAddress(), dummyAddress); - assert.deepEqual(interaction.getFunction(), new ContractFunction("getUltimateAnswer")); - assert.lengthOf(interaction.getArguments(), 0); - assert.deepEqual(interaction.getGasLimit(), 543210n); - - provider.mockQueryContractOnFunction( - "getUltimateAnswer", - new SmartContractQueryResponse({ - returnDataParts: [Buffer.from([42])], - returnCode: "ok", - returnMessage: "msg", - function: "getUltimateAnswer", - }), - ); - - // Query; - - const interactionQuery = interaction.buildQuery(); - let response = await controller.query({ - contract: interactionQuery.address, - arguments: interactionQuery.getEncodedArguments(), - function: interactionQuery.func.toString(), - caller: interactionQuery.caller, - value: BigInt(interactionQuery.value.toString()), - }); - assert.isTrue(response.length == 1); - assert.deepEqual(response[0], new BigNumber(42)); - - // Execute, do not wait for execution - let transaction = interaction.withSender(alice.address).withNonce(0n).buildTransaction(); - transaction.sender = alice.address; - transaction.signature = await alice.signTransaction(transaction); - let hash = await provider.sendTransaction(transaction); - assert.equal(transaction.nonce, 0n); - assert.equal(transaction.data.toString(), "getUltimateAnswer"); - assert.equal(hash, "3579ad09099feb9755c860ddd225251170806d833342e912fccdfe2ed5c3a364"); - - transaction = interaction.withNonce(1n).buildTransaction(); - transaction.sender = alice.address; - transaction.signature = await alice.signTransaction(transaction); - hash = await provider.sendTransaction(transaction); - assert.equal(transaction.nonce, 1n); - assert.equal(hash, "ad513ce7c5d371d30e48f073326899766736eac1ac231d847d45bc3facbcb496"); - - // Execute, and wait for execution - transaction = interaction.withNonce(2n).buildTransaction(); - transaction.sender = alice.address; - transaction.signature = await alice.signTransaction(transaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@2bs", "getUltimateAnswer"); - hash = await provider.sendTransaction(transaction); - let responseExecute = await controller.awaitCompletedExecute(hash); - - assert.isTrue(responseExecute.values.length == 1); - assert.deepEqual(responseExecute.values[0], new BigNumber(43)); - assert.isTrue(responseExecute.returnCode == "ok"); - }); - - it("should interact with 'counter'", async function () { - this.timeout(30000); - let abi = await loadAbiRegistry("src/testdata/counter.abi.json"); - let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let controller = new SmartContractController({ chainID: "D", networkProvider: provider, abi: abi }); - - let getInteraction = contract.methodsExplicit.get(); - let incrementInteraction = (contract.methods.increment()).withGasLimit(543210n); - let decrementInteraction = (contract.methods.decrement()).withGasLimit(987654n); - - // For "get()", return fake 7 - provider.mockQueryContractOnFunction( - "get", - new SmartContractQueryResponse({ - returnDataParts: [Buffer.from([7])], - returnCode: "ok", - function: "get", - returnMessage: "", - }), - ); - - // Query "get()" - const interactionQuery = getInteraction.buildQuery(); - let response = await controller.query({ - contract: interactionQuery.address, - arguments: interactionQuery.getEncodedArguments(), - function: interactionQuery.func.toString(), - caller: interactionQuery.caller, - value: BigInt(interactionQuery.value.toString()), - }); - assert.deepEqual(response[0], new BigNumber(7)); - - let incrementTransaction = incrementInteraction - .withSender(alice.address) - .withNonce(14n) - .withChainID("mock") - .buildTransaction(); - - incrementTransaction.signature = await alice.signTransaction(incrementTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@08", "increment"); - let hash = await provider.sendTransaction(incrementTransaction); - let responseExecute = await controller.awaitCompletedExecute(hash); - assert.deepEqual(responseExecute.values[0], new BigNumber(8)); - - // Decrement three times (simulate three parallel broadcasts). Wait for execution of the latter (third transaction). Return fake "5". - // Decrement #1 - let decrementTransaction = decrementInteraction - .withSender(alice.address) - .withNonce(15n) - .withChainID("mock") - .buildTransaction(); - - decrementTransaction.signature = await alice.signTransaction(decrementTransaction); - await provider.sendTransaction(decrementTransaction); - // Decrement #2 - decrementTransaction = decrementInteraction.withNonce(16n).buildTransaction(); - decrementTransaction.signature = await alice.signTransaction(decrementTransaction); - await provider.sendTransaction(decrementTransaction); - // Decrement #3 - - decrementTransaction = decrementInteraction.withNonce(17n).buildTransaction(); - decrementTransaction.signature = await alice.signTransaction(decrementTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@05", "decrement"); - hash = await provider.sendTransaction(decrementTransaction); - responseExecute = await controller.awaitCompletedExecute(hash); - assert.deepEqual(responseExecute.values[0], new BigNumber(5)); - }); - - it("should interact with 'lottery-esdt'", async function () { - this.timeout(30000); - let abi = await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"); - let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let controller = new SmartContractController({ chainID: "D", networkProvider: provider, abi: abi }); - - let startInteraction = ( - contract.methodsExplicit - .start([ - BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue("lucky-token"), - new BigUIntValue(1), - OptionValue.newMissing(), - OptionValue.newMissing(), - OptionValue.newProvided(new U32Value(1)), - OptionValue.newMissing(), - OptionValue.newMissing(), - OptionalValue.newMissing(), - ]) - .withGasLimit(5000000n) - ); - - let statusInteraction = contract.methods.status(["lucky"]).withGasLimit(5000000n); - - let getLotteryInfoInteraction = contract.methods.getLotteryInfo(["lucky"]).withGasLimit(5000000n); - - // start() - let startTransaction = startInteraction - .withSender(alice.address) - .withNonce(14n) - .withChainID("mock") - .buildTransaction(); - - startTransaction.signature = await alice.signTransaction(startTransaction); - - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b", "start"); - let hash = await provider.sendTransaction(startTransaction); - let response = await controller.awaitCompletedExecute(hash); - - assert.equal(startTransaction.data.toString(), "start@6c75636b79@6c75636b792d746f6b656e@01@@@0100000001@@"); - assert.isTrue(response.returnCode == "ok"); - assert.isTrue(response.values.length == 0); - - // status() (this is a view function, but for the sake of the test, we'll execute it) - let statusTransaction = statusInteraction - .withSender(alice.address) - .withNonce(15n) - .withChainID("mock") - .buildTransaction(); - - statusTransaction.signature = await alice.signTransaction(statusTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@01", "status"); - - hash = await provider.sendTransaction(startTransaction); - response = await controller.awaitCompletedExecute(hash); - - assert.equal(statusTransaction.data.toString(), "status@6c75636b79"); - assert.isTrue(response.returnCode == "ok"); - assert.isTrue(response.values.length == 1); - assert.deepEqual(response.values[0]!.valueOf(), { name: "Running", fields: [] }); - - // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let getLotteryInfoTransaction = getLotteryInfoInteraction - .withSender(alice.address) - .withNonce(15n) - .withChainID("mock") - .buildTransaction(); - - getLotteryInfoTransaction.signature = await alice.signTransaction(getLotteryInfoTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult( - "@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000", - "getLotteryInfo", - ); - hash = await provider.sendTransaction(startTransaction); - response = await controller.awaitCompletedExecute(hash); - assert.equal(getLotteryInfoTransaction.data.toString(), "getLotteryInfo@6c75636b79"); - assert.isTrue(response.returnCode == "ok"); - assert.isTrue(response.values.length == 1); - - assert.deepEqual(response.values[0]!.valueOf(), { - token_identifier: "lucky-token", - ticket_price: new BigNumber("1"), - tickets_left: new BigNumber(0), - deadline: new BigNumber("0x000000005fc2b9db", 16), - max_entries_per_user: new BigNumber(0xffffffff), - prize_distribution: Buffer.from([0x64]), - prize_pool: new BigNumber("94720000000000000000000"), - }); - }); -}); diff --git a/src/abi/interaction.ts b/src/abi/interaction.ts deleted file mode 100644 index 2dc379b52..000000000 --- a/src/abi/interaction.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Account } from "../accounts"; -import { Address } from "../core/address"; -import { Compatibility } from "../core/compatibility"; -import { TRANSACTION_VERSION_DEFAULT } from "../core/constants"; -import { TokenTransfer } from "../core/tokens"; -import { Transaction } from "../core/transaction"; -import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; -import { SmartContractTransactionsFactory } from "../smartContracts"; -import { ContractFunction } from "./function"; -import { CallArguments } from "./interface"; -import { Query } from "./query"; -import { EndpointDefinition, TypedValue } from "./typesystem"; - -/** - * Internal interface: the smart contract, as seen from the perspective of an {@link Interaction}. - */ -interface ISmartContractWithinInteraction { - call({ func, args, value, gasLimit, receiver }: CallArguments): Transaction; - getAddress(): Address; - getEndpoint(name: ContractFunction): EndpointDefinition; -} - -/** - * @deprecated component. Use "SmartContractTransactionsFactory" or "SmartContractController", instead. - * - * Interactions can be seen as mutable transaction & query builders. - * - * Aside from building transactions and queries, the interactors are also responsible for interpreting - * the execution outcome for the objects they've built. - */ -export class Interaction { - private readonly contract: ISmartContractWithinInteraction; - private readonly function: ContractFunction; - private readonly args: TypedValue[]; - - private nonce: bigint = 0n; - private value: bigint = 0n; - private gasLimit: bigint = 0n; - private gasPrice: bigint | undefined = undefined; - private chainID: string = ""; - private querent: Address = Address.empty(); - private explicitReceiver?: Address; - private sender: Address = Address.empty(); - private version: number = TRANSACTION_VERSION_DEFAULT; - - private tokenTransfers: TokenTransfer[]; - - constructor(contract: ISmartContractWithinInteraction, func: ContractFunction, args: TypedValue[]) { - this.contract = contract; - this.function = func; - this.args = args; - this.tokenTransfers = []; - } - - getContractAddress(): Address { - return this.contract.getAddress(); - } - - getFunction(): ContractFunction { - return this.function; - } - - getEndpoint(): EndpointDefinition { - return this.contract.getEndpoint(this.function); - } - - getArguments(): TypedValue[] { - return this.args; - } - - getValue(): bigint { - return this.value; - } - - getTokenTransfers(): TokenTransfer[] { - return this.tokenTransfers; - } - - getGasLimit(): bigint { - return this.gasLimit; - } - - getExplicitReceiver(): Address | undefined { - return this.explicitReceiver; - } - - buildTransaction(): Transaction { - Compatibility.guardAddressIsSetAndNonZero( - this.sender, - "'sender' of interaction", - "use interaction.withSender()", - ); - - const factoryConfig = new TransactionsFactoryConfig({ chainID: this.chainID.valueOf() }); - const factory = new SmartContractTransactionsFactory({ - config: factoryConfig, - }); - - const transaction = factory.createTransactionForExecute(this.sender, { - contract: this.contract.getAddress(), - function: this.function.valueOf(), - gasLimit: BigInt(this.gasLimit.valueOf()), - arguments: this.args, - nativeTransferAmount: BigInt(this.value.toString()), - tokenTransfers: this.tokenTransfers, - }); - - transaction.chainID = this.chainID.valueOf(); - transaction.nonce = BigInt(this.nonce.valueOf()); - transaction.version = this.version; - - if (this.gasPrice) { - transaction.gasPrice = BigInt(this.gasPrice.valueOf()); - } - - return transaction; - } - - buildQuery(): Query { - return new Query({ - address: this.contract.getAddress(), - func: this.function, - args: this.args, - // Value will be set using "withValue()". - value: this.value, - caller: this.querent, - }); - } - - withValue(value: bigint): Interaction { - this.value = value; - return this; - } - - withSingleESDTTransfer(transfer: TokenTransfer): Interaction { - this.tokenTransfers = [transfer].map((transfer) => new TokenTransfer(transfer)); - return this; - } - - withSingleESDTNFTTransfer(transfer: TokenTransfer): Interaction { - this.tokenTransfers = [transfer].map((transfer) => new TokenTransfer(transfer)); - return this; - } - - withMultiESDTNFTTransfer(transfers: TokenTransfer[]): Interaction { - this.tokenTransfers = transfers.map((transfer) => new TokenTransfer(transfer)); - return this; - } - - withGasLimit(gasLimit: bigint): Interaction { - this.gasLimit = gasLimit; - return this; - } - - withGasPrice(gasPrice: bigint): Interaction { - this.gasPrice = gasPrice; - return this; - } - - withNonce(nonce: bigint): Interaction { - this.nonce = nonce; - return this; - } - - useThenIncrementNonceOf(account: Account): Interaction { - return this.withNonce(account.getNonceThenIncrement()); - } - - withChainID(chainID: string): Interaction { - this.chainID = chainID; - return this; - } - - withSender(sender: Address): Interaction { - this.sender = sender; - return this; - } - - withVersion(version: number): Interaction { - this.version = version; - return this; - } - - /** - * Sets the "caller" field on contract queries. - */ - withQuerent(querent: Address): Interaction { - this.querent = querent; - return this; - } - - withExplicitReceiver(receiver: Address): Interaction { - this.explicitReceiver = receiver; - return this; - } -} diff --git a/src/abi/nativeSerializer.ts b/src/abi/nativeSerializer.ts index 5ea5dc133..0e0c4542d 100644 --- a/src/abi/nativeSerializer.ts +++ b/src/abi/nativeSerializer.ts @@ -403,7 +403,7 @@ export namespace NativeSerializer { native: NativeTypes.NativeAddress, errorContext: ArgumentErrorContext, ): Address { - if ((native).bech32) { + if ((native).toBech32) { return
native; } if ((native).getAddress) { diff --git a/src/abi/smartContract.local.net.spec.ts b/src/abi/smartContract.local.net.spec.ts deleted file mode 100644 index 9e97bbfb6..000000000 --- a/src/abi/smartContract.local.net.spec.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { assert } from "chai"; -import { promises } from "fs"; -import { Account } from "../accounts"; -import { Logger } from "../core/logger"; -import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; -import { TransactionWatcher } from "../core/transactionWatcher"; -import { - SmartContractController, - SmartContractTransactionsFactory, - SmartContractTransactionsOutcomeParser, -} from "../smartContracts"; -import { createLocalnetProvider } from "../testutils/networkProviders"; -import { getTestWalletsPath, stringifyBigIntJSON } from "../testutils/utils"; -import { decodeUnsignedNumber } from "./codec"; -import { SmartContract } from "./smartContract"; -import { - AddressValue, - BigUIntValue, - BytesValue, - OptionalValue, - OptionValue, - TokenIdentifierValue, - U32Value, -} from "./typesystem"; - -describe("test on local testnet", function () { - let alice: Account, bob: Account, carol: Account; - let provider = createLocalnetProvider(); - let watcher: TransactionWatcher; - let parser: SmartContractTransactionsOutcomeParser; - - before(async function () { - alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); - bob = await Account.newFromPem(`${getTestWalletsPath()}/bob.pem`); - carol = await Account.newFromPem(`${getTestWalletsPath()}/carol.pem`); - - watcher = new TransactionWatcher( - { - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash); - }, - }, - { - pollingIntervalMilliseconds: 5000, - timeoutMilliseconds: 50000, - }, - ); - parser = new SmartContractTransactionsOutcomeParser(); - }); - - it("counter: should deploy, then simulate transactions using SmartContractTransactionsFactory", async function () { - this.timeout(60000); - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ config: config }); - - const bytecode = await promises.readFile("src/testdata/counter.wasm"); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 4000000n, - }); - deployTransaction.nonce = alice.nonce; - - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - - const smartContractCallTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "increment", - gasLimit: 4000000n, - }); - alice.incrementNonce(); - smartContractCallTransaction.nonce = alice.getNonceThenIncrement(); - smartContractCallTransaction.signature = await alice.signTransaction(smartContractCallTransaction); - - const simulateOne = factory.createTransactionForExecute(alice.address, { - function: "increment", - contract: contractAddress, - gasLimit: 200000n, - }); - simulateOne.nonce = alice.getNonceThenIncrement(); - simulateOne.signature = await alice.signTransaction(simulateOne); - - const simulateTwo = factory.createTransactionForExecute(alice.address, { - function: "foobar", - contract: contractAddress, - gasLimit: 700000n, - }); - simulateTwo.nonce = alice.getNonceThenIncrement(); - simulateTwo.signature = await alice.signTransaction(simulateTwo); - - const simulateThree = factory.createTransactionForExecute(alice.address, { - function: "foobar", - contract: contractAddress, - gasLimit: 700000n, - }); - simulateThree.nonce = alice.getNonceThenIncrement(); - simulateThree.signature = await alice.signTransaction(simulateThree); - - // Broadcast & execute - const deployTxHash = await provider.sendTransaction(deployTransaction); - const callTxHash = await provider.sendTransaction(smartContractCallTransaction); - await watcher.awaitCompleted(deployTxHash); - let transactionOnNetwork = await provider.getTransaction(deployTxHash); - let response = parser.parseExecute({ transactionOnNetwork }); - - assert.isTrue(response.returnCode == "ok"); - - await watcher.awaitCompleted(callTxHash); - transactionOnNetwork = await provider.getTransaction(callTxHash); - response = parser.parseExecute({ transactionOnNetwork }); - assert.isTrue(response.returnCode == "ok"); - - // Simulate - Logger.trace(stringifyBigIntJSON(await provider.simulateTransaction(simulateOne))); - Logger.trace(stringifyBigIntJSON(await provider.simulateTransaction(simulateTwo))); - }); - - it("counter: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { - this.timeout(80000); - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ config: config }); - - alice.nonce = (await provider.getAccount(alice.address)).nonce; - const bytecode = await promises.readFile("src/testdata/counter.wasm"); - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 3000000n, - }); - deployTransaction.nonce = alice.nonce; - - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - alice.incrementNonce(); - const firstScCallTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "increment", - gasLimit: 3000000n, - }); - firstScCallTransaction.nonce = alice.getNonceThenIncrement(); - firstScCallTransaction.signature = await alice.signTransaction(firstScCallTransaction); - - const secondScCallTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "increment", - gasLimit: 3000000n, - }); - secondScCallTransaction.nonce = alice.getNonceThenIncrement(); - secondScCallTransaction.signature = await alice.signTransaction(secondScCallTransaction); - - // Broadcast & execute - const deployTxHash = await provider.sendTransaction(deployTransaction); - const firstScCallHash = await provider.sendTransaction(firstScCallTransaction); - const secondScCallHash = await provider.sendTransaction(secondScCallTransaction); - - await watcher.awaitCompleted(deployTxHash); - await watcher.awaitCompleted(firstScCallHash); - await watcher.awaitCompleted(secondScCallHash); - - // Check counter - const smartContractQueriesController = new SmartContractController({ - chainID: "localnet", - networkProvider: provider, - }); - - const query = smartContractQueriesController.createQuery({ - contract: contractAddress, - function: "get", - arguments: [], - }); - - const queryResponse = await smartContractQueriesController.runQuery(query); - assert.lengthOf(queryResponse.returnDataParts, 1); - assert.equal(3, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); - }); - - it("erc20: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { - this.timeout(60000); - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ config: config }); - - alice.nonce = (await provider.getAccount(alice.address)).nonce; - const bytecode = await promises.readFile("src/testdata/erc20.wasm"); - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 50000000n, - arguments: [new U32Value(10000)], - }); - deployTransaction.nonce = alice.nonce; - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - alice.incrementNonce(); - const transactionMintBob = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "transferToken", - gasLimit: 9000000n, - arguments: [new AddressValue(bob.address), new U32Value(1000)], - }); - transactionMintBob.nonce = alice.getNonceThenIncrement(); - transactionMintBob.signature = await alice.signTransaction(transactionMintBob); - - const transactionMintCarol = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "transferToken", - gasLimit: 9000000n, - arguments: [new AddressValue(carol.address), new U32Value(1500)], - }); - transactionMintCarol.nonce = alice.getNonceThenIncrement(); - transactionMintCarol.signature = await alice.signTransaction(transactionMintCarol); - - // Broadcast & execute - const deployTxHash = await provider.sendTransaction(deployTransaction); - const mintBobTxHash = await provider.sendTransaction(transactionMintBob); - const mintCarolTxHash = await provider.sendTransaction(transactionMintCarol); - - await watcher.awaitCompleted(deployTxHash); - await watcher.awaitCompleted(mintBobTxHash); - await watcher.awaitCompleted(mintCarolTxHash); - - // Query state, do some assertions - const smartContractController = new SmartContractController({ - chainID: "localnet", - networkProvider: provider, - }); - - let query = smartContractController.createQuery({ - contract: contractAddress, - function: "totalSupply", - arguments: [], - }); - let queryResponse = await smartContractController.runQuery(query); - assert.lengthOf(queryResponse.returnDataParts, 1); - assert.equal(10000, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); - - query = smartContractController.createQuery({ - contract: contractAddress, - function: "balanceOf", - arguments: [new AddressValue(alice.address)], - }); - queryResponse = await smartContractController.runQuery(query); - assert.equal(7500, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); - - query = smartContractController.createQuery({ - contract: contractAddress, - function: "balanceOf", - arguments: [new AddressValue(bob.address)], - }); - queryResponse = await smartContractController.runQuery(query); - assert.equal(1000, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); - - query = smartContractController.createQuery({ - contract: contractAddress, - function: "balanceOf", - arguments: [new AddressValue(carol.address)], - }); - queryResponse = await smartContractController.runQuery(query); - assert.equal(1500, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); - }); - - it("lottery: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { - this.timeout(60000); - let network = await provider.getNetworkConfig(); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ config: config }); - - const bytecode = await promises.readFile("src/testdata/lottery-esdt.wasm"); - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 50000000n, - }); - deployTransaction.nonce = alice.nonce; - - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - alice.incrementNonce(); - const startTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "start", - gasLimit: 10000000n, - arguments: [ - BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue("EGLD"), - new BigUIntValue(1), - OptionValue.newMissing(), - OptionValue.newMissing(), - OptionValue.newProvided(new U32Value(1)), - OptionValue.newMissing(), - OptionValue.newMissing(), - OptionalValue.newMissing(), - ], - }); - startTransaction.nonce = alice.getNonceThenIncrement(); - startTransaction.signature = await alice.signTransaction(startTransaction); - - // Broadcast & execute - const deployTx = await provider.sendTransaction(deployTransaction); - const startTx = await provider.sendTransaction(startTransaction); - - await watcher.awaitAllEvents(deployTx, ["SCDeploy"]); - await watcher.awaitAnyEvent(startTx, ["completedTxEvent"]); - - // Let's check the SCRs - let transactionOnNetwork = await provider.getTransaction(deployTx); - let response = parser.parseExecute({ transactionOnNetwork }); - assert.isTrue(response.returnCode == "ok"); - - transactionOnNetwork = await provider.getTransaction(startTx); - response = parser.parseExecute({ transactionOnNetwork }); - assert.isTrue(response.returnCode == "ok"); - - // Query state, do some assertions - const smartContractQueriesController = new SmartContractController({ - chainID: "localnet", - networkProvider: provider, - }); - - let query = smartContractQueriesController.createQuery({ - contract: contractAddress, - function: "status", - arguments: [BytesValue.fromUTF8("lucky")], - }); - let queryResponse = await smartContractQueriesController.runQuery(query); - assert.equal(decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0])), 1); - - query = smartContractQueriesController.createQuery({ - contract: contractAddress, - function: "status", - arguments: [BytesValue.fromUTF8("missingLottery")], - }); - queryResponse = await smartContractQueriesController.runQuery(query); - assert.equal(decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0])), 0); - }); -}); diff --git a/src/abi/smartContract.spec.ts b/src/abi/smartContract.spec.ts deleted file mode 100644 index 273783100..000000000 --- a/src/abi/smartContract.spec.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { assert } from "chai"; -import { Account } from "../accounts"; -import { Address } from "../core/address"; -import { TransactionComputer } from "../core/transactionComputer"; -import { TransactionStatus } from "../core/transactionStatus"; -import { TransactionWatcher } from "../core/transactionWatcher"; -import { getTestWalletsPath, MarkCompleted, MockNetworkProvider, Wait } from "../testutils"; -import { Code } from "./code"; -import { ContractFunction } from "./function"; -import { SmartContract } from "./smartContract"; -import { Abi, OptionalValue, U32Value, U8Value, VariadicValue } from "./typesystem"; -import { BytesValue } from "./typesystem/bytes"; - -describe("test contract", () => { - let provider = new MockNetworkProvider(); - let chainID = "test"; - let alice: Account; - const computer = new TransactionComputer(); - - before(async function () { - alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); - }); - - it("should compute contract address", async () => { - let owner = new Address("93ee6143cdc10ce79f15b2a6c2ad38e9b6021c72a1779051f47154fd54cfbd5e"); - - let firstContractAddress = SmartContract.computeAddress(owner, 0n); - assert.equal(firstContractAddress.toBech32(), "erd1qqqqqqqqqqqqqpgqhdjjyq8dr7v5yq9tv6v5vt9tfvd00vg7h40q6779zn"); - - let secondContractAddress = SmartContract.computeAddress(owner, 1n); - assert.equal( - secondContractAddress.toBech32(), - "erd1qqqqqqqqqqqqqpgqde8eqjywyu6zlxjxuxqfg5kgtmn3setxh40qen8egy", - ); - }); - - it("should deploy", async () => { - let watcher = new TransactionWatcher(provider, { - pollingIntervalMilliseconds: 42, - timeoutMilliseconds: 42 * 42, - }); - - let contract = new SmartContract(); - let deployTransaction = contract.deploy({ - code: Code.fromBuffer(Buffer.from([1, 2, 3, 4])), - gasLimit: 1000000n, - chainID: chainID, - deployer: alice.address, - }); - - deployTransaction.nonce = alice.nonce; - - assert.equal(deployTransaction.data.toString(), "01020304@0500@0100"); - assert.equal(deployTransaction.gasLimit, 1000000n); - assert.equal(deployTransaction.nonce, alice.nonce); - - // Compute & set the contract address - contract.setAddress(SmartContract.computeAddress(alice.address, 42n)); - assert.equal( - contract.getAddress().toBech32(), - "erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q", - ); - - // Sign the transaction - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - // Now let's broadcast the deploy transaction, and wait for its execution. - let hash = await provider.sendTransaction(deployTransaction); - const hashString = computer.computeTransactionHash(deployTransaction); - - await Promise.all([ - provider.mockTransactionTimeline(deployTransaction, [ - new Wait(40), - new TransactionStatus("pending"), - new Wait(40), - new TransactionStatus("executed"), - new MarkCompleted(), - ]), - watcher.awaitCompleted(hashString), - ]); - - assert.isTrue((await provider.getTransaction(hash)).status.isCompleted()); - }); - - it("should call", async () => { - let watcher = new TransactionWatcher(provider, { - pollingIntervalMilliseconds: 42, - timeoutMilliseconds: 42 * 42, - }); - - let contract = new SmartContract({ - address: new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"), - }); - - alice.nonce = 42n; - - let callTransactionOne = contract.call({ - func: new ContractFunction("helloEarth"), - args: [new U32Value(5), BytesValue.fromHex("0123")], - gasLimit: 150000n, - chainID: chainID, - caller: alice.address, - }); - - let callTransactionTwo = contract.call({ - func: new ContractFunction("helloMars"), - args: [new U32Value(5), BytesValue.fromHex("0123")], - gasLimit: 1500000n, - chainID: chainID, - caller: alice.address, - }); - - callTransactionOne.nonce = alice.nonce; - alice.incrementNonce(); - callTransactionTwo.nonce = alice.nonce; - - assert.equal(callTransactionOne.nonce, 42n); - assert.equal(callTransactionOne.data.toString(), "helloEarth@05@0123"); - assert.equal(callTransactionOne.gasLimit, 150000n); - assert.equal(callTransactionTwo.nonce, 43n); - assert.equal(callTransactionTwo.data.toString(), "helloMars@05@0123"); - assert.equal(callTransactionTwo.gasLimit, 1500000n); - - // Sign transactions, broadcast them - callTransactionOne.signature = await alice.signTransaction(callTransactionOne); - callTransactionTwo.signature = await alice.signTransaction(callTransactionTwo); - - let hashOne = await provider.sendTransaction(callTransactionOne); - let hashTwo = await provider.sendTransaction(callTransactionTwo); - - await Promise.all([ - provider.mockTransactionTimeline(callTransactionOne, [ - new Wait(40), - new TransactionStatus("pending"), - new Wait(40), - new TransactionStatus("executed"), - new MarkCompleted(), - ]), - provider.mockTransactionTimeline(callTransactionTwo, [ - new Wait(40), - new TransactionStatus("pending"), - new Wait(40), - new TransactionStatus("executed"), - new MarkCompleted(), - ]), - watcher.awaitCompleted(hashOne), - watcher.awaitCompleted(hashTwo), - ]); - - assert.isTrue((await provider.getTransaction(hashOne)).status.isCompleted()); - assert.isTrue((await provider.getTransaction(hashTwo)).status.isCompleted()); - }); - - it("should upgrade", async () => { - let watcher = new TransactionWatcher(provider, { - pollingIntervalMilliseconds: 42, - timeoutMilliseconds: 42 * 42, - }); - - let contract = new SmartContract(); - contract.setAddress(Address.newFromBech32("erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q")); - - let deployTransaction = contract.upgrade({ - code: Code.fromBuffer(Buffer.from([1, 2, 3, 4])), - gasLimit: 1000000n, - chainID: chainID, - caller: alice.address, - }); - - alice.nonce = 42n; - deployTransaction.nonce = alice.nonce; - - assert.equal(deployTransaction.data.toString(), "upgradeContract@01020304@0100"); - assert.equal(deployTransaction.gasLimit, 1000000n); - assert.equal(deployTransaction.nonce, 42n); - - // Sign the transaction - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - // Now let's broadcast the deploy transaction, and wait for its execution. - let hash = await provider.sendTransaction(deployTransaction); - - await Promise.all([ - provider.mockTransactionTimeline(deployTransaction, [ - new Wait(40), - new TransactionStatus("pending"), - new Wait(40), - new TransactionStatus("executed"), - new MarkCompleted(), - ]), - watcher.awaitCompleted(hash), - ]); - - assert.isTrue((await provider.getTransaction(hash)).status.isCompleted()); - }); - - it("v13 should be stricter than v12 on optional> (exotic) parameters (since NativeSerializer is used under the hood)", async () => { - // Related to: https://github.com/multiversx/mx-sdk-js-core/issues/435. - // In v12, contract.call() only supported TypedValue[] as contract call arguments. - // In v13, NativeSerializer is used under the hood, which allows one to mix typed values with native values. - // However, this comes with additional rules regarding optional> parameters. - // These parameters are exotic and, generally speaking, can be avoided in contracts: - // https://docs.multiversx.com/developers/data/multi-values/ - - const abi = Abi.create({ - endpoints: [ - { - name: "foo", - inputs: [ - { - type: "optional>", - }, - ], - }, - ], - }); - - const callerAddress = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqaxa53w6uk43n6dhyt2la6cd5lyv32qn4396qfsqlnk"); - - const contract = new SmartContract({ - abi, - address: contractAddress, - }); - - // This was possible in v12 (more permissive). - // In v12, contract.call() required TypedValue[] for "args". - assert.throws(() => { - contract.call({ - func: "foo", - args: [new U8Value(1), new U8Value(2), new U8Value(3)], - chainID: "D", - gasLimit: 1000000n, - caller: callerAddress, - }); - }, "Wrong number of arguments for endpoint foo: expected between 0 and 1 arguments, have 3"); - - // In v13, the contract.call() would be as follows: - contract.call({ - func: "foo", - args: [[new U8Value(1), new U8Value(2), new U8Value(3)]], - chainID: "D", - gasLimit: 1000000n, - caller: callerAddress, - }); - - // Or simply: - contract.call({ - func: "foo", - args: [[1, 2, 3]], - chainID: "D", - gasLimit: 1000000n, - caller: callerAddress, - }); - - // This did not work in v12, it does not work in v13 either (since it's imprecise / incorrect). - assert.throws(() => { - contract.methods.foo([1, 2, 3]); - }, "Wrong number of arguments for endpoint foo: expected between 0 and 1 arguments, have 3"); - - const endpointFoo = abi.getEndpoint("foo"); - const optionalVariadicType = endpointFoo.input[0].type; - const variadicTypedValue = VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3)); - - // However, all these were and are still possible: - contract.methodsExplicit.foo([new U8Value(1), new U8Value(2), new U8Value(3)]); - contract.methods.foo([new OptionalValue(optionalVariadicType, variadicTypedValue)]); - contract.methods.foo([variadicTypedValue]); - contract.methods.foo([[new U8Value(1), new U8Value(2), new U8Value(3)]]); - contract.methods.foo([[new U8Value(1), 2, 3]]); - }); - - it("v13 should be stricter than v12 on variadic parameters (since NativeSerializer is used under the hood)", async () => { - const abi = Abi.create({ - endpoints: [ - { - name: "foo", - inputs: [ - { - type: "variadic", - }, - ], - }, - ], - }); - - const callerAddress = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqaxa53w6uk43n6dhyt2la6cd5lyv32qn4396qfsqlnk"); - - const contract = new SmartContract({ - abi, - address: contractAddress, - }); - - // This was possible in v12 (more permissive). - // In v12, contract.call() required TypedValue[] for "args". - assert.throws(() => { - contract.call({ - func: "foo", - args: [new U8Value(1), new U8Value(2), new U8Value(3)], - chainID: "D", - gasLimit: 1000000n, - caller: callerAddress, - }); - }, "Invalid argument: Wrong argument type for endpoint foo: typed value provided; expected variadic type, have U8Value"); - - // In v13, the contract.call() would be as follows: - contract.call({ - func: "foo", - args: [VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3))], - chainID: "D", - gasLimit: 1000000n, - caller: callerAddress, - }); - - // Or simply: - contract.call({ - func: "foo", - args: [1, 2, 3], - chainID: "D", - gasLimit: 1000000n, - caller: callerAddress, - }); - - // However, all these were and are still possible: - contract.methods.foo([1, 2, 3]); - contract.methodsExplicit.foo([new U8Value(1), new U8Value(2), new U8Value(3)]); - contract.methods.foo([VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3))]); - }); -}); diff --git a/src/abi/smartContract.ts b/src/abi/smartContract.ts deleted file mode 100644 index 5c991eef7..000000000 --- a/src/abi/smartContract.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { Address, AddressComputer } from "../core/address"; -import { CodeMetadata } from "../core/codeMetadata"; -import { Compatibility } from "../core/compatibility"; -import { TRANSACTION_MIN_GAS_PRICE } from "../core/constants"; -import { ErrContractHasNoAddress } from "../core/errors"; -import { Transaction } from "../core/transaction"; -import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; -import { guardValueIsSet } from "../core/utils"; -import { SmartContractTransactionsFactory } from "./../smartContracts/smartContractTransactionsFactory"; -import { ContractFunction } from "./function"; -import { Interaction } from "./interaction"; -import { - CallArguments, - DeployArguments, - ICodeMetadata, - ISmartContract, - QueryArguments, - UpgradeArguments, -} from "./interface"; -import { NativeSerializer } from "./nativeSerializer"; -import { Query } from "./query"; -import { Abi, EndpointDefinition, TypedValue } from "./typesystem"; - -/** - * * @deprecated component. Use "SmartContractTransactionsFactory" or "SmartContractController", instead. - * - * An abstraction for deploying and interacting with Smart Contracts. - */ -export class SmartContract implements ISmartContract { - private address: Address = Address.empty(); - private abi?: Abi; - - /** - * This object contains a function for each endpoint defined by the contract. - * (a bit similar to web3js's "contract.methods"). - */ - public readonly methodsExplicit: { [key: string]: (args?: TypedValue[]) => Interaction } = {}; - - /** - * This object contains a function for each endpoint defined by the contract. - * (a bit similar to web3js's "contract.methods"). - * - * This is an alternative to {@link methodsExplicit}. - * Unlike {@link methodsExplicit}, automatic type inference (wrt. ABI) is applied when using {@link methods}. - */ - public readonly methods: { [key: string]: (args?: any[]) => Interaction } = {}; - - /** - * Create a SmartContract object by providing its address on the Network. - */ - constructor(options: { address?: Address; abi?: Abi } = {}) { - this.address = options.address || Address.empty(); - this.abi = options.abi; - - if (this.abi) { - this.setupMethods(); - } - } - - private setupMethods() { - // eslint-disable-next-line @typescript-eslint/no-this-alias - let contract = this; - let abi = this.getAbi(); - - for (const definition of abi.getEndpoints()) { - let functionName = definition.name; - - // For each endpoint defined by the ABI, we attach a function to the "methods" and "methodsAuto" objects, - // a function that receives typed values as arguments - // and returns a prepared contract interaction. - this.methodsExplicit[functionName] = function (args?: TypedValue[]) { - let func = new ContractFunction(functionName); - let interaction = new Interaction(contract, func, args || []); - return interaction; - }; - - this.methods[functionName] = function (args?: any[]) { - let func = new ContractFunction(functionName); - // Perform automatic type inference, wrt. the endpoint definition: - let typedArgs = NativeSerializer.nativeToTypedValues(args || [], definition); - let interaction = new Interaction(contract, func, typedArgs || []); - return interaction; - }; - } - } - - /** - * Sets the address, as on Network. - */ - setAddress(address: Address) { - this.address = address; - } - - /** - * Gets the address, as on Network. - */ - getAddress(): Address { - return this.address; - } - - private getAbi(): Abi { - guardValueIsSet("abi", this.abi); - return this.abi!; - } - - getEndpoint(name: string | ContractFunction): EndpointDefinition { - if (typeof name === "string") { - return this.getAbi().getEndpoint(name); - } - return this.getAbi().getEndpoint(name.name); - } - - /** - * Creates a {@link Transaction} for deploying the Smart Contract to the Network. - */ - deploy({ - deployer, - code, - codeMetadata, - initArguments, - value, - gasLimit, - gasPrice, - chainID, - }: DeployArguments): Transaction { - Compatibility.guardAddressIsSetAndNonZero( - deployer, - "'deployer' of SmartContract.deploy()", - "pass the actual address to deploy()", - ); - - const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); - const factory = new SmartContractTransactionsFactory({ - config: config, - abi: this.abi, - }); - - const bytecode = Buffer.from(code.toString(), "hex"); - const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata); - - const transaction = factory.createTransactionForDeploy(deployer, { - bytecode: bytecode, - gasLimit: BigInt(gasLimit.valueOf()), - arguments: initArguments ?? [], - isUpgradeable: metadataAsJson.upgradeable, - isReadable: metadataAsJson.readable, - isPayable: metadataAsJson.payable, - isPayableBySmartContract: metadataAsJson.payableBySc, - }); - - transaction.chainID = chainID; - transaction.value = value ?? 0n; - transaction.gasPrice = gasPrice ?? BigInt(TRANSACTION_MIN_GAS_PRICE); - - return transaction; - } - - private getMetadataPropertiesAsObject(codeMetadata?: ICodeMetadata): { - upgradeable: boolean; - readable: boolean; - payable: boolean; - payableBySc: boolean; - } { - let metadata: CodeMetadata; - if (codeMetadata) { - metadata = CodeMetadata.newFromBytes(Buffer.from(codeMetadata.toString(), "hex")); - } else { - metadata = new CodeMetadata(); - } - const metadataAsJson = metadata.toJSON() as { - upgradeable: boolean; - readable: boolean; - payable: boolean; - payableBySc: boolean; - }; - - return metadataAsJson; - } - - /** - * Creates a {@link Transaction} for upgrading the Smart Contract on the Network. - */ - upgrade({ - caller, - code, - codeMetadata, - initArguments, - value, - gasLimit, - gasPrice, - chainID, - }: UpgradeArguments): Transaction { - Compatibility.guardAddressIsSetAndNonZero( - caller, - "'caller' of SmartContract.upgrade()", - "pass the actual address to upgrade()", - ); - - this.ensureHasAddress(); - - const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); - const factory = new SmartContractTransactionsFactory({ - config: config, - abi: this.abi, - }); - - const bytecode = Uint8Array.from(Buffer.from(code.toString(), "hex")); - const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata); - - const transaction = factory.createTransactionForUpgrade(caller, { - contract: this.getAddress(), - bytecode: bytecode, - gasLimit: BigInt(gasLimit.valueOf()), - arguments: initArguments ?? [], - isUpgradeable: metadataAsJson.upgradeable, - isReadable: metadataAsJson.readable, - isPayable: metadataAsJson.payable, - isPayableBySmartContract: metadataAsJson.payableBySc, - }); - - transaction.chainID = chainID; - transaction.value = value ?? 0n; - transaction.gasPrice = gasPrice ?? BigInt(TRANSACTION_MIN_GAS_PRICE); - - return transaction; - } - - /** - * Creates a {@link Transaction} for calling (a function of) the Smart Contract. - */ - call({ func, args, value, gasLimit, receiver, gasPrice, chainID, caller }: CallArguments): Transaction { - Compatibility.guardAddressIsSetAndNonZero( - caller, - "'caller' of SmartContract.call()", - "pass the actual address to call()", - ); - - this.ensureHasAddress(); - - const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); - const factory = new SmartContractTransactionsFactory({ - config: config, - abi: this.abi, - }); - - args = args || []; - value = value || 0n; - - const transaction = factory.createTransactionForExecute(caller, { - contract: receiver ? receiver : this.getAddress(), - function: func.toString(), - gasLimit: gasLimit, - arguments: args, - }); - - transaction.chainID = chainID; - transaction.value = value; - transaction.gasPrice = gasPrice ?? BigInt(TRANSACTION_MIN_GAS_PRICE); - - return transaction; - } - - createQuery({ func, args, value, caller }: QueryArguments): Query { - this.ensureHasAddress(); - - return new Query({ - address: this.getAddress(), - func: func, - args: args, - value: value, - caller: caller, - }); - } - - private ensureHasAddress() { - if (!this.getAddress().toBech32()) { - throw new ErrContractHasNoAddress(); - } - } - - /** - * Computes the address of a Smart Contract. - * The address is computed deterministically, from the address of the owner and the nonce of the deployment transaction. - * - * @param owner The owner of the Smart Contract - * @param nonce The owner nonce used for the deployment transaction - */ - static computeAddress(owner: Address, nonce: bigint): Address { - const addressComputer = new AddressComputer(); - return addressComputer.computeContractAddress(owner, BigInt(nonce.valueOf())); - } -} diff --git a/src/abi/smartContractResults.local.net.spec.ts b/src/abi/smartContractResults.local.net.spec.ts deleted file mode 100644 index b66dbbd51..000000000 --- a/src/abi/smartContractResults.local.net.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { assert } from "chai"; -import { promises } from "fs"; -import { Account } from "../accounts"; -import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; -import { TransactionWatcher } from "../core/transactionWatcher"; -import { SmartContractTransactionsFactory, SmartContractTransactionsOutcomeParser } from "../smartContracts"; -import { getTestWalletsPath, prepareDeployment } from "../testutils"; -import { createLocalnetProvider } from "../testutils/networkProviders"; -import { ContractFunction } from "./function"; -import { SmartContract } from "./smartContract"; - -describe("fetch transactions from local testnet", function () { - let alice: Account; - let provider = createLocalnetProvider(); - let watcher: TransactionWatcher; - let parser: SmartContractTransactionsOutcomeParser; - - before(async function () { - alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); - watcher = new TransactionWatcher( - { - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash); - }, - }, - { - pollingIntervalMilliseconds: 5000, - timeoutMilliseconds: 50000, - }, - ); - - parser = new SmartContractTransactionsOutcomeParser(); - }); - - it("counter smart contract", async function () { - this.timeout(60000); - - let network = await provider.getNetworkConfig(); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - // Deploy - let contract = new SmartContract({}); - - let transactionDeploy = await prepareDeployment({ - contract: contract, - deployer: alice, - codePath: "src/testdata/counter.wasm", - gasLimit: 3000000n, - initArguments: [], - chainID: network.chainID, - }); - - // ++ - let transactionIncrement = contract.call({ - func: new ContractFunction("increment"), - gasLimit: 3000000n, - chainID: network.chainID, - caller: alice.address, - }); - - transactionIncrement.nonce = alice.getNonceThenIncrement(); - transactionIncrement.signature = await alice.signTransaction(transactionIncrement); - - // Broadcast & execute - const txHashDeploy = await provider.sendTransaction(transactionDeploy); - const txHashIncrement = await provider.sendTransaction(transactionIncrement); - - await watcher.awaitCompleted(txHashDeploy); - await watcher.awaitCompleted(txHashIncrement); - - const transactionOnNetworkDeploy = await provider.getTransaction(txHashDeploy); - const transactionOnNetworkIncrement = await provider.getTransaction(txHashIncrement); - - let response = parser.parseExecute({ transactionOnNetwork: transactionOnNetworkDeploy }); - assert.isTrue(response.returnCode == "ok"); - - response = parser.parseExecute({ transactionOnNetwork: transactionOnNetworkIncrement }); - assert.isTrue(response.returnCode == "ok"); - }); - - it("interact with counter smart contract using SmartContractTransactionsFactory", async function () { - this.timeout(60000); - - let network = await provider.getNetworkConfig(); - - const config = new TransactionsFactoryConfig({ chainID: network.chainID }); - const factory = new SmartContractTransactionsFactory({ config: config }); - - const bytecode = await promises.readFile("src/testdata/counter.wasm"); - alice.nonce = (await provider.getAccount(alice.address)).nonce; - - const deployTransaction = factory.createTransactionForDeploy(alice.address, { - bytecode: bytecode, - gasLimit: 3000000n, - }); - deployTransaction.nonce = alice.nonce; - deployTransaction.signature = await alice.signTransaction(deployTransaction); - - const contractAddress = SmartContract.computeAddress(alice.address, alice.nonce); - alice.incrementNonce(); - const smartContractCallTransaction = factory.createTransactionForExecute(alice.address, { - contract: contractAddress, - function: "increment", - gasLimit: 3000000n, - }); - smartContractCallTransaction.nonce = alice.getNonceThenIncrement(); - smartContractCallTransaction.signature = await alice.signTransaction(smartContractCallTransaction); - - // Broadcast & execute - const deployTxHash = await provider.sendTransaction(deployTransaction); - const callTxHash = await provider.sendTransaction(smartContractCallTransaction); - - await watcher.awaitCompleted(deployTxHash); - await watcher.awaitCompleted(callTxHash); - - let transactionOnNetworkDeploy = await provider.getTransaction(deployTxHash); - let transactionOnNetworkIncrement = await provider.getTransaction(callTxHash); - - let response = parser.parseExecute({ transactionOnNetwork: transactionOnNetworkDeploy }); - assert.isTrue(response.returnCode == "ok"); - - response = parser.parseExecute({ transactionOnNetwork: transactionOnNetworkIncrement }); - assert.isTrue(response.returnCode == "ok"); - }); -}); diff --git a/src/accountManagement/accountController.ts b/src/accountManagement/accountController.ts index 746ba12dc..52fa99f95 100644 --- a/src/accountManagement/accountController.ts +++ b/src/accountManagement/accountController.ts @@ -1,5 +1,5 @@ import { Address, BaseController, BaseControllerInput } from "../core"; -import { IAccount } from "../core/interfaces"; +import { IAccount, IGasLimitEstimator } from "../core/interfaces"; import { Transaction } from "../core/transaction"; import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig"; import { AccountTransactionsFactory } from "./accountTransactionsFactory"; @@ -8,10 +8,11 @@ import { SaveKeyValueInput, SetGuardianInput } from "./resources"; export class AccountController extends BaseController { private factory: AccountTransactionsFactory; - constructor(options: { chainID: string }) { + constructor(options: { chainID: string; gasLimitEstimator?: IGasLimitEstimator }) { super(); this.factory = new AccountTransactionsFactory({ config: new TransactionsFactoryConfig(options), + gasLimitEstimator: options.gasLimitEstimator, }); } @@ -20,7 +21,7 @@ export class AccountController extends BaseController { nonce: bigint, options: SaveKeyValueInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSavingKeyValue(sender.address, options); + const transaction = await this.factory.createTransactionForSavingKeyValue(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -37,7 +38,7 @@ export class AccountController extends BaseController { nonce: bigint, options: SetGuardianInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingGuardian(sender.address, options); + const transaction = await this.factory.createTransactionForSettingGuardian(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -54,7 +55,7 @@ export class AccountController extends BaseController { nonce: bigint, options: { relayer?: Address; gasPrice?: bigint; gasLimit?: bigint }, ): Promise { - const transaction = this.factory.createTransactionForGuardingAccount(sender.address); + const transaction = await this.factory.createTransactionForGuardingAccount(sender.address); transaction.relayer = options.relayer ?? Address.empty(); transaction.nonce = nonce; this.setTransactionGasOptions(transaction, options); @@ -68,9 +69,10 @@ export class AccountController extends BaseController { nonce: bigint, options: BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnguardingAccount(sender.address); + const transaction = await this.factory.createTransactionForUnguardingAccount(sender.address, { + guardian: options.guardian, + }); - transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); transaction.nonce = nonce; this.setTransactionGasOptions(transaction, options); diff --git a/src/accountManagement/accountTransactionsFactory.spec.ts b/src/accountManagement/accountTransactionsFactory.spec.ts index 2bee04226..6589a8c2e 100644 --- a/src/accountManagement/accountTransactionsFactory.spec.ts +++ b/src/accountManagement/accountTransactionsFactory.spec.ts @@ -1,5 +1,6 @@ import { assert } from "chai"; import { Address, TransactionsFactoryConfig } from "../core"; +import { TRANSACTION_OPTIONS_TX_GUARDED } from "../core/constants"; import { AccountTransactionsFactory } from "./accountTransactionsFactory"; describe("test account transactions factory", function () { @@ -10,7 +11,7 @@ describe("test account transactions factory", function () { const sender = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const keyValuePairs = new Map([[Buffer.from("key0"), Buffer.from("value0")]]); - const transaction = factory.createTransactionForSavingKeyValue(sender, { + const transaction = await factory.createTransactionForSavingKeyValue(sender, { keyValuePairs: keyValuePairs, }); @@ -33,7 +34,7 @@ describe("test account transactions factory", function () { const guardian = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const serviceID = "MultiversXTCSService"; - const transaction = factory.createTransactionForSettingGuardian(sender, { + const transaction = await factory.createTransactionForSettingGuardian(sender, { guardianAddress: guardian, serviceID: serviceID, }); @@ -58,7 +59,7 @@ describe("test account transactions factory", function () { it("should create 'Transaction' for guarding account", async function () { const sender = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const transaction = factory.createTransactionForGuardingAccount(sender); + const transaction = await factory.createTransactionForGuardingAccount(sender); assert.deepEqual( transaction.sender, @@ -74,10 +75,35 @@ describe("test account transactions factory", function () { assert.equal(transaction.gasLimit, 318000n); }); - it("should create 'Transaction' for unguarding account", async function () { + it("should create 'Transaction' for unguarding account with guardian", async function () { + const sender = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const guardian = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await factory.createTransactionForUnguardingAccount(sender, { guardian }); + + assert.deepEqual( + transaction.sender, + Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + ); + assert.deepEqual( + transaction.receiver, + Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + ); + assert.equal(Buffer.from(transaction.data).toString(), "UnGuardAccount"); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 321000n); + assert.equal(transaction.options, TRANSACTION_OPTIONS_TX_GUARDED); + assert.deepEqual( + transaction.guardian, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ); + }); + + it("should create 'Transaction' for unguarding account without guardian", async function () { const sender = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const transaction = factory.createTransactionForUnguardingAccount(sender); + const transaction = await factory.createTransactionForUnguardingAccount(sender, {}); assert.deepEqual( transaction.sender, @@ -91,5 +117,6 @@ describe("test account transactions factory", function () { assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); assert.equal(transaction.gasLimit, 321000n); + assert.equal(transaction.options, 0); }); }); diff --git a/src/accountManagement/accountTransactionsFactory.ts b/src/accountManagement/accountTransactionsFactory.ts index a87fd37ef..0b128bd1f 100644 --- a/src/accountManagement/accountTransactionsFactory.ts +++ b/src/accountManagement/accountTransactionsFactory.ts @@ -1,6 +1,8 @@ +import { IGasLimitEstimator } from "../core"; import { Address } from "../core/address"; +import { BaseFactory } from "../core/baseFactory"; +import { TRANSACTION_OPTIONS_TX_GUARDED } from "../core/constants"; import { Transaction } from "../core/transaction"; -import { TransactionBuilder } from "../core/transactionBuilder"; import { SaveKeyValueInput, SetGuardianInput } from "./resources"; interface IConfig { @@ -15,27 +17,31 @@ interface IConfig { gasLimitUnguardAccount: bigint; } -export class AccountTransactionsFactory { +export class AccountTransactionsFactory extends BaseFactory { private readonly config: IConfig; - constructor(options: { config: IConfig }) { + constructor(options: { config: IConfig; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); this.config = options.config; } - createTransactionForSavingKeyValue(sender: Address, options: SaveKeyValueInput): Transaction { + async createTransactionForSavingKeyValue(sender: Address, options: SaveKeyValueInput): Promise { const functionName = "SaveKeyValue"; const keyValueParts = this.computeDataPartsForSavingKeyValue(options.keyValuePairs); const dataParts = [functionName, ...keyValueParts]; const extraGas = this.computeExtraGasForSavingKeyValue(options.keyValuePairs); - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: sender, - dataParts: dataParts, - gasLimit: extraGas, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, extraGas); + + return transaction; } private computeExtraGasForSavingKeyValue(keyValuePairs: Map): bigint { @@ -60,46 +66,63 @@ export class AccountTransactionsFactory { return dataParts; } - createTransactionForSettingGuardian(sender: Address, options: SetGuardianInput): Transaction { + async createTransactionForSettingGuardian(sender: Address, options: SetGuardianInput): Promise { const dataParts = [ "SetGuardian", options.guardianAddress.toHex(), Buffer.from(options.serviceID).toString("hex"), ]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetGuardian, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetGuardian); + + return transaction; } - createTransactionForGuardingAccount(sender: Address): Transaction { + async createTransactionForGuardingAccount(sender: Address): Promise { const dataParts = ["GuardAccount"]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitGuardAccount, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitGuardAccount); + + return transaction; } - createTransactionForUnguardingAccount(sender: Address): Transaction { + async createTransactionForUnguardingAccount( + sender: Address, + options: { guardian?: Address }, + ): Promise { const dataParts = ["UnGuardAccount"]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitUnguardAccount, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + if (options.guardian) { + transaction.guardian = options.guardian; + transaction.options = TRANSACTION_OPTIONS_TX_GUARDED; + } + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitUnguardAccount); + + return transaction; } } diff --git a/src/core/address.ts b/src/core/address.ts index 652ade8ef..2b9eab400 100644 --- a/src/core/address.ts +++ b/src/core/address.ts @@ -93,15 +93,6 @@ export class Address { return new Address(pubkey, hrp); } - /** - * @deprecated Use {@link newFromBech32} instead. - */ - static fromBech32(value: string): Address { - // On this legacy flow, we do not accept addresses with custom hrp (in order to avoid behavioral breaking changes). - const { hrp, pubkey } = decodeFromBech32({ value, allowCustomHrp: false }); - return new Address(pubkey, hrp); - } - /** * Named constructor * Creates an address object from a hex-encoded string @@ -114,24 +105,6 @@ export class Address { return new Address(Buffer.from(value, "hex"), hrp); } - /** - * @deprecated Use {@link newFromHex} instead. - */ - static fromHex(value: string, hrp?: string): Address { - return Address.newFromHex(value, hrp); - } - - private static isValidHex(value: string) { - return Buffer.from(value, "hex").length == PUBKEY_LENGTH; - } - - /** - * @deprecated Use {@link toHex} instead. - */ - hex(): string { - return this.toHex(); - } - /** * Returns the hex representation of the address (pubkey) */ @@ -143,13 +116,6 @@ export class Address { return this.publicKey.toString("hex"); } - /** - * @deprecated Use {@link toBech32} instead. - */ - bech32(): string { - return this.toBech32(); - } - /** * Returns the bech32 representation of the address */ @@ -163,13 +129,6 @@ export class Address { return address; } - /** - * @deprecated Use {@link getPublicKey} instead. - */ - pubkey(): Buffer { - return this.getPublicKey(); - } - /** * Returns the underlying public key. */ @@ -207,6 +166,10 @@ export class Address { return true; } + private static isValidHex(value: string) { + return Buffer.from(value, "hex").length == PUBKEY_LENGTH; + } + /** * Returns whether the address is empty. */ @@ -250,13 +213,6 @@ export class Address { return new Address("0".repeat(64)); } - /** - * @deprecated Use {@link isSmartContract} instead. - */ - isContractAddress(): boolean { - return this.isSmartContract(); - } - /** * Returns whether the address is a smart contract address. */ diff --git a/src/core/baseFactory.ts b/src/core/baseFactory.ts new file mode 100644 index 000000000..7816f9e10 --- /dev/null +++ b/src/core/baseFactory.ts @@ -0,0 +1,52 @@ +import { ARGUMENTS_SEPARATOR } from "./constants"; +import { IGasLimitEstimator } from "./interfaces"; +import { Transaction } from "./transaction"; + +interface Config { + minGasLimit: bigint; + gasLimitPerByte: bigint; +} + +/** + * @internal + */ +export class BaseFactory { + private gasConfig: Config; + private gasLimitEstimator?: IGasLimitEstimator; + + constructor(options: { config: Config; gasLimitEstimator?: IGasLimitEstimator }) { + this.gasConfig = options.config; + this.gasLimitEstimator = options.gasLimitEstimator; + } + + protected setTransactionPayload(transaction: Transaction, dataParts: string[]): void { + const data = dataParts.join(ARGUMENTS_SEPARATOR); + transaction.data = Buffer.from(data); + } + + /** + * Sets the gas limit for the transaction. + * @param gasLimit - Optional gas limit to set. This is the value provided by the user. + * @param configGasLimit - Optional gas limit from the configuration. This is computed internally based on some config values. + */ + protected async setGasLimit(transaction: Transaction, gasLimit?: bigint, configGasLimit?: bigint): Promise { + if (gasLimit !== undefined) { + transaction.gasLimit = gasLimit; + return; + } + + if (this.gasLimitEstimator) { + transaction.gasLimit = await this.gasLimitEstimator.estimateGasLimit({ transaction }); + return; + } + + if (configGasLimit !== undefined) { + const dataMovementGas = + this.gasConfig.minGasLimit + this.gasConfig.gasLimitPerByte * BigInt(transaction.data.length); + transaction.gasLimit = dataMovementGas + configGasLimit; + return; + } + + throw new Error("Either provide a `gasLimit` parameter or initialize the factory with a `gasLimitEstimator`."); + } +} diff --git a/src/core/codeMetadata.ts b/src/core/codeMetadata.ts index 77c682866..205b930c5 100644 --- a/src/core/codeMetadata.ts +++ b/src/core/codeMetadata.ts @@ -62,14 +62,6 @@ export class CodeMetadata { return new CodeMetadata(upgradeable, readable, payable, payableBySc); } - /** - * @deprecated Use {@link newFromBytes} instead. - * Creates a metadata object from a buffer. - */ - static newFromBuffer(buffer: Buffer): CodeMetadata { - return this.newFromBytes(buffer); - } - /** * Converts the metadata to the protocol-friendly representation. */ @@ -93,29 +85,6 @@ export class CodeMetadata { return new Uint8Array(Buffer.from([byteZero, byteOne])); } - /** - * @deprecated Use {@link toBytes} instead. - */ - toBuffer(): Buffer { - let byteZero = 0; - let byteOne = 0; - - if (this.upgradeable) { - byteZero |= CodeMetadata.ByteZero.Upgradeable; - } - if (this.readable) { - byteZero |= CodeMetadata.ByteZero.Readable; - } - if (this.payable) { - byteOne |= CodeMetadata.ByteOne.Payable; - } - if (this.payableBySc) { - byteOne |= CodeMetadata.ByteOne.PayableBySc; - } - - return Buffer.from([byteZero, byteOne]); - } - /** * Converts the metadata to a hex-encoded string. */ diff --git a/src/core/errors.ts b/src/core/errors.ts index 793d2c3f2..076585f04 100644 --- a/src/core/errors.ts +++ b/src/core/errors.ts @@ -213,7 +213,7 @@ export class ErrContractHasNoAddress extends ErrContract { public constructor() { super(` The smart contract has no address set. Make sure you provide the address in the constructor, or call setAddress() appropriately. -If you need to recompute the address of the contract, make use of SmartContract.computeAddress() (static method). +If you need to recompute the address of the contract, make use of SmartContract.computeAddress() (static method). `); } } @@ -439,3 +439,12 @@ export class ExpectedAccountConditionNotReachedError extends Err { super("The expected account condition was not reached."); } } + +/** + * Signals that the gasLimit could not be estimated. + */ +export class ErrGasLimitCannotBeEstimated extends Err { + public constructor(error: Error) { + super(`Failed to estimate gas limit: ${error}`, error); + } +} diff --git a/src/core/interfaces.ts b/src/core/interfaces.ts index ff273b6a8..6e75e1dd0 100644 --- a/src/core/interfaces.ts +++ b/src/core/interfaces.ts @@ -43,3 +43,7 @@ export interface INetworkConfig { gasPriceModifier: number; chainID: string; } + +export interface IGasLimitEstimator { + estimateGasLimit(options: { transaction: Transaction }): Promise; +} diff --git a/src/core/tokens.spec.ts b/src/core/tokens.spec.ts index 08f75e446..cab6a65cb 100644 --- a/src/core/tokens.spec.ts +++ b/src/core/tokens.spec.ts @@ -92,7 +92,7 @@ describe("test token transfer", () => { const nonce = 1n; const transfer = new TokenTransfer({ token: new Token({ identifier, nonce }), amount: 1n }); - assert.equal(transfer.tokenIdentifier, identifier); + assert.equal(transfer.token.identifier, identifier); assert.equal(transfer.token.nonce, nonce); assert.equal(transfer.amount, 1n); }); diff --git a/src/core/tokens.ts b/src/core/tokens.ts index e7567c50f..8016eef93 100644 --- a/src/core/tokens.ts +++ b/src/core/tokens.ts @@ -1,23 +1,6 @@ -import BigNumber from "bignumber.js"; import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "./constants"; -import { ErrInvalidArgument, ErrInvalidTokenIdentifier } from "./errors"; +import { ErrInvalidTokenIdentifier } from "./errors"; import { numberToPaddedHex } from "./utils.codec"; - -// Legacy constants: -const EGLDTokenIdentifier = "EGLD"; -const EGLDNumDecimals = 18; - -// Legacy configuration. -// Note: this will actually set the default rounding mode for all BigNumber objects in the environment (in the application / dApp). -BigNumber.set({ ROUNDING_MODE: 1 }); - -interface ILegacyTokenTransferOptions { - tokenIdentifier: string; - nonce: number; - amountAsBigInteger: BigNumber.Value; - numDecimals?: number; -} - export type TokenType = "NFT" | "SFT" | "META" | "FNG"; export class Token { @@ -37,58 +20,9 @@ export class TokenTransfer { readonly token: Token; readonly amount: bigint; - /** - * @deprecated field. Use "token.identifier" instead. - */ - readonly tokenIdentifier: string; - - /** - * @deprecated field. Use "token.nonce" instead. - */ - readonly nonce: number; - - /** - * @deprecated field. Use "amount" instead. - */ - readonly amountAsBigInteger: BigNumber; - - /** - * @deprecated field. The number of decimals is not a concern of "sdk-core". - * For formatting and parsing amounts, use "sdk-dapp" or "bignumber.js" directly. - */ - readonly numDecimals: number; - - constructor(options: { token: Token; amount: bigint } | ILegacyTokenTransferOptions) { - if (this.isLegacyTokenTransferOptions(options)) { - // Handle legacy fields. - const amount = new BigNumber(options.amountAsBigInteger); - if (!amount.isInteger() || amount.isNegative()) { - throw new ErrInvalidArgument(`bad amountAsBigInteger: ${options.amountAsBigInteger}`); - } - - this.tokenIdentifier = options.tokenIdentifier; - this.nonce = options.nonce; - this.amountAsBigInteger = amount; - this.numDecimals = options.numDecimals || 0; - - // Handle new fields. - this.token = new Token({ - identifier: options.tokenIdentifier, - nonce: BigInt(options.nonce), - }); - - this.amount = BigInt(this.amountAsBigInteger.toFixed(0)); - } else { - // Handle new fields. - this.token = options.token; - this.amount = options.amount; - - // Handle legacy fields. - this.tokenIdentifier = options.token.identifier; - this.nonce = Number(options.token.nonce); - this.amountAsBigInteger = new BigNumber(this.amount.toString()); - this.numDecimals = 0; - } + constructor(options: { token: Token; amount: bigint }) { + this.token = options.token; + this.amount = options.amount; } /** * @@ -100,139 +34,9 @@ export class TokenTransfer { return new TokenTransfer({ token, amount }); } - private isLegacyTokenTransferOptions(options: any): options is ILegacyTokenTransferOptions { - return options.tokenIdentifier !== undefined; - } - - /** - * @deprecated Use {@link newFromNativeAmount} instead. - */ - static egldFromAmount(amount: BigNumber.Value) { - const amountAsBigInteger = new BigNumber(amount).shiftedBy(EGLDNumDecimals).decimalPlaces(0); - return this.egldFromBigInteger(amountAsBigInteger); - } - - /** - * @deprecated Use {@link newFromNativeAmount} instead. - */ - static egldFromBigInteger(amountAsBigInteger: BigNumber.Value) { - return new TokenTransfer({ - tokenIdentifier: EGLDTokenIdentifier, - nonce: 0, - amountAsBigInteger, - numDecimals: EGLDNumDecimals, - }); - } - - /** - * @deprecated Use the constructor instead: new TokenTransfer({ token, amount }); - */ - static fungibleFromAmount(tokenIdentifier: string, amount: BigNumber.Value, numDecimals: number) { - const amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); - return this.fungibleFromBigInteger(tokenIdentifier, amountAsBigInteger, numDecimals); - } - - /** - * @deprecated Use the constructor instead: new TokenTransfer({ token, amount }); - */ - static fungibleFromBigInteger( - tokenIdentifier: string, - amountAsBigInteger: BigNumber.Value, - numDecimals: number = 0, - ) { - return new TokenTransfer({ - tokenIdentifier, - nonce: 0, - amountAsBigInteger, - numDecimals, - }); - } - - /** - * @deprecated Use the constructor instead: new TokenTransfer({ token, amount }); - */ - static nonFungible(tokenIdentifier: string, nonce: number) { - return new TokenTransfer({ - tokenIdentifier, - nonce, - amountAsBigInteger: 1, - numDecimals: 0, - }); - } - - /** - * @deprecated Use the constructor instead: new TokenTransfer({ token, amount }); - */ - static semiFungible(tokenIdentifier: string, nonce: number, quantity: number) { - return new TokenTransfer({ - tokenIdentifier, - nonce, - amountAsBigInteger: quantity, - numDecimals: 0, - }); - } - - /** - * @deprecated Use the constructor instead: new TokenTransfer({ token, amount }); - */ - static metaEsdtFromAmount(tokenIdentifier: string, nonce: number, amount: BigNumber.Value, numDecimals: number) { - const amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); - return this.metaEsdtFromBigInteger(tokenIdentifier, nonce, amountAsBigInteger, numDecimals); - } - - /** - * @deprecated Use the constructor instead: new TokenTransfer({ token, amount }); - */ - static metaEsdtFromBigInteger( - tokenIdentifier: string, - nonce: number, - amountAsBigInteger: BigNumber.Value, - numDecimals = 0, - ) { - return new TokenTransfer({ - tokenIdentifier, - nonce, - amountAsBigInteger, - numDecimals, - }); - } - toString() { return this.amount.toString(); } - - /** - * @deprecated Use the "amount" field instead. - */ - valueOf(): BigNumber { - return new BigNumber(this.amount.toString()); - } - - /** - * @deprecated For formatting and parsing amounts, use "sdk-dapp" or "bignumber.js" directly. - */ - toPrettyString(): string { - return `${this.toAmount()} ${this.tokenIdentifier}`; - } - - private toAmount(): string { - return this.amountAsBigInteger.shiftedBy(-this.numDecimals).toFixed(this.numDecimals); - } - - /** - * @deprecated Within your code, don't mix native values (EGLD) and custom (ESDT) tokens. - * See "TransferTransactionsFactory.createTransactionForNativeTokenTransfer()" vs. "TransferTransactionsFactory.createTransactionForESDTTokenTransfer()". - */ - isEgld(): boolean { - return this.token.identifier == EGLDTokenIdentifier; - } - - /** - * @deprecated Use "TokenComputer.isFungible(token)" instead. - */ - isFungible(): boolean { - return this.token.nonce == 0n; - } } export class TokenComputer { diff --git a/src/core/transaction.local.net.spec.ts b/src/core/transaction.local.net.spec.ts index 1d67876bf..f5367e806 100644 --- a/src/core/transaction.local.net.spec.ts +++ b/src/core/transaction.local.net.spec.ts @@ -162,7 +162,7 @@ describe("test transaction", function () { await bob.sync(provider); const initialBalanceOfBob = new BigNumber((await bob.getBalance(provider)).toString()); - const transaction = factory.createTransactionForNativeTokenTransfer(alice.address, { + const transaction = await factory.createTransactionForNativeTokenTransfer(alice.address, { receiver: bob.address, nativeAmount: 42000000000000000000n, }); diff --git a/src/core/transaction.ts b/src/core/transaction.ts index 00de7a681..bc02b96e7 100644 --- a/src/core/transaction.ts +++ b/src/core/transaction.ts @@ -1,8 +1,6 @@ -import { BigNumber } from "bignumber.js"; import { Address } from "./address"; import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_VERSION_DEFAULT } from "./constants"; -import { INetworkConfig, IPlainTransactionObject } from "./interfaces"; -import { interpretSignatureAsBuffer } from "./signature"; +import { IPlainTransactionObject } from "./interfaces"; import { TransactionComputer } from "./transactionComputer"; /** @@ -137,202 +135,6 @@ export class Transaction { this.relayerSignature = options.relayerSignature || Buffer.from([]); } - /** - * @deprecated method, use {@link nonce} property instead. - */ - getNonce(): bigint { - return this.nonce; - } - - /** - * @deprecated method, use {@link nonce} property instead. - * Sets the account sequence number of the sender. Must be done prior signing. - */ - setNonce(nonce: bigint) { - this.nonce = nonce; - } - - /** - * @deprecated method, use {@link value} property instead. - */ - getValue(): bigint { - return this.value; - } - - /** - * @deprecated method, use {@link value} property instead. - */ - setValue(value: bigint) { - this.value = value; - } - - /** - * @deprecated method, use {@link sender} property instead. - */ - getSender(): Address { - return this.sender; - } - - /** - * @deprecated method, use {@link sender} property instead. - */ - setSender(sender: Address) { - this.sender = sender; - } - - /** - * @deprecated method, use {@link receiver} property instead. - */ - getReceiver(): Address { - return this.receiver; - } - - /** - * @deprecated method, use {@link senderUsername} property instead. - */ - getSenderUsername(): string { - return this.senderUsername; - } - - /** - * @deprecated method, use {@link senderUsername} property instead. - */ - setSenderUsername(senderUsername: string) { - this.senderUsername = senderUsername; - } - - /** - * @deprecated method, use {@link receiverUsername} property instead. - */ - getReceiverUsername(): string { - return this.receiverUsername; - } - - /** - * @deprecated method, use {@link receiverUsername} property instead. - */ - setReceiverUsername(receiverUsername: string) { - this.receiverUsername = receiverUsername; - } - - /** - * @deprecated method, use {@link guardian} property instead. - */ - getGuardian(): Address { - return this.guardian; - } - - /** - * @deprecated method, use {@link gasPrice} property instead. - */ - getGasPrice(): bigint { - return this.gasPrice; - } - - /** - * @deprecated method, use {@link gasPrice} property instead. - */ - setGasPrice(gasPrice: bigint) { - this.gasPrice = gasPrice; - } - - /** - * @deprecated method, use {@link gasLimit} property instead. - */ - getGasLimit(): bigint { - return this.gasLimit; - } - - /** - * @deprecated method, use {@link gasLimit} property instead. - */ - setGasLimit(gasLimit: bigint) { - this.gasLimit = gasLimit; - } - - /** - * @deprecated method, use {@link data} property instead. - */ - getData(): Uint8Array { - return this.data; - } - - /** - * @deprecated method, use {@link chainID} property instead. - */ - getChainID(): string { - return this.chainID; - } - - /** - * @deprecated method, use {@link chainID} property instead. - */ - setChainID(chainID: string) { - this.chainID = chainID; - } - - /** - * @deprecated method, use {@link version} property instead. - */ - getVersion(): number { - return this.version; - } - - /** - * @deprecated method, use {@link version} property instead. - */ - setVersion(version: number) { - this.version = version; - } - - /** - * @deprecated method, use {@link options} property instead. - */ - getOptions(): number { - return this.options; - } - - /** - * @deprecated method, use {@link options} property instead. - * - * Question for review: check how the options are set by sdk-dapp, wallet, ledger, extension. - */ - setOptions(options: number) { - this.options = options; - } - - /** - * @deprecated method, use{@link signature} property instead. - */ - getSignature(): Buffer { - return Buffer.from(this.signature); - } - - /** - * @deprecated method, use {@link guardianSignature} property instead. - */ - getGuardianSignature(): Buffer { - return Buffer.from(this.guardianSignature); - } - - /** - * @deprecated method, use {@link guardian} property instead. - */ - setGuardian(guardian: Address) { - this.guardian = guardian; - } - - /** - * @deprecated method, use "TransactionComputer.computeBytesForSigning()" instead. - * Serializes a transaction to a sequence of bytes, ready to be signed. - * This function is called internally by signers. - */ - serializeForSigning(): Buffer { - const computer = new TransactionComputer(); - const bytes = computer.computeBytesForSigning(this); - return Buffer.from(bytes); - } - /** * Checks the integrity of the guarded transaction */ @@ -371,16 +173,6 @@ export class Transaction { return plainObject; } - /** - * @deprecated method, use {@link toPlainObject} instead. - * Converts a plain object transaction into a Transaction Object. - * - * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() - */ - static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { - return Transaction.newFromPlainObject(plainObjectTransaction); - } - /** * Converts a plain object transaction into a Transaction Object. * @@ -414,26 +206,6 @@ export class Transaction { return transaction; } - /** - * @deprecated method, use {@link signature} property instead. - * Applies the signature on the transaction. - * - * @param signature The signature, as computed by a signer. - */ - applySignature(signature: Uint8Array) { - this.signature = interpretSignatureAsBuffer(signature); - } - - /** - * @deprecated method, use {@link guardianSignature} property instead. - * Applies the guardian signature on the transaction. - * - * @param guardianSignature The signature, as computed by a signer. - */ - applyGuardianSignature(guardianSignature: Uint8Array) { - this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); - } - /** * Converts a transaction to a ready-to-broadcast object. * Called internally by the network provider. @@ -442,18 +214,6 @@ export class Transaction { return this.toPlainObject(); } - /** - * @deprecated method, use "TransactionComputer.computeTransactionFee()" instead. - * - * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties - * @param networkConfig {@link NetworkConfig} - */ - computeFee(networkConfig: INetworkConfig): BigNumber { - const computer = new TransactionComputer(); - const fee = computer.computeTransactionFee(this, networkConfig); - return new BigNumber(fee.toString()); - } - private toBase64OrUndefined(value?: string | Uint8Array) { return value && value.length ? Buffer.from(value).toString("base64") : undefined; } diff --git a/src/core/transactionBuilder.ts b/src/core/transactionBuilder.ts deleted file mode 100644 index d7e46d4bc..000000000 --- a/src/core/transactionBuilder.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Address } from "./address"; -import { ARGUMENTS_SEPARATOR } from "./constants"; -import { Transaction } from "./transaction"; - -interface Config { - chainID: string; - minGasLimit: bigint; - gasLimitPerByte: bigint; -} - -/** - * @internal - */ -export class TransactionBuilder { - private config: Config; - private sender: Address; - private receiver: Address; - private dataParts: string[]; - private providedGasLimit: bigint; - private addDataMovementGas: boolean; - private amount?: bigint; - - constructor(options: { - config: Config; - sender: Address; - receiver: Address; - dataParts: string[]; - gasLimit: bigint; - addDataMovementGas: boolean; - amount?: bigint; - }) { - this.config = options.config; - this.sender = options.sender; - this.receiver = options.receiver; - this.dataParts = options.dataParts; - this.providedGasLimit = options.gasLimit; - this.addDataMovementGas = options.addDataMovementGas; - this.amount = options.amount; - } - - private computeGasLimit(payload: Uint8Array): bigint { - if (!this.addDataMovementGas) { - return this.providedGasLimit; - } - - const dataMovementGas = this.config.minGasLimit + this.config.gasLimitPerByte * BigInt(payload.length); - const gasLimit = dataMovementGas + this.providedGasLimit; - return gasLimit; - } - - private buildTransactionPayload(): Uint8Array { - const data = this.dataParts.join(ARGUMENTS_SEPARATOR); - return Buffer.from(data); - } - - build(): Transaction { - const data = this.buildTransactionPayload(); - const gasLimit = this.computeGasLimit(data); - - return new Transaction({ - sender: this.sender, - receiver: this.receiver, - gasLimit: gasLimit, - value: this.amount || 0n, - data: data.valueOf(), - chainID: this.config.chainID, - }); - } -} diff --git a/src/core/transactionStatus.ts b/src/core/transactionStatus.ts index e13ee7b84..797b6c70f 100644 --- a/src/core/transactionStatus.ts +++ b/src/core/transactionStatus.ts @@ -28,14 +28,6 @@ export class TransactionStatus { return this.status == "received" || this.status == "pending"; } - /** - * Returns whether the transaction has been executed (not necessarily with success). - * @deprecated This will be remove next version, please use {@link isCompleted} instead. - */ - isExecuted(): boolean { - return this.isSuccessful() || this.isFailed() || this.isInvalid(); - } - /** * Returns whether the transaction has been conpleted (not necessarily with success). */ diff --git a/src/delegation/delegationController.ts b/src/delegation/delegationController.ts index 882f024b6..a8622e53f 100644 --- a/src/delegation/delegationController.ts +++ b/src/delegation/delegationController.ts @@ -3,6 +3,7 @@ import { BaseController, BaseControllerInput, IAccount, + IGasLimitEstimator, Transaction, TransactionOnNetwork, TransactionsFactoryConfig, @@ -18,11 +19,16 @@ export class DelegationController extends BaseController { private factory: DelegationTransactionsFactory; private parser: DelegationTransactionsOutcomeParser; - constructor(options: { chainID: string; networkProvider: INetworkProvider }) { + constructor(options: { + chainID: string; + networkProvider: INetworkProvider; + gasLimitEstimator?: IGasLimitEstimator; + }) { super(); this.transactionAwaiter = new TransactionWatcher(options.networkProvider); this.factory = new DelegationTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), + gasLimitEstimator: options.gasLimitEstimator, }); this.parser = new DelegationTransactionsOutcomeParser(); } @@ -32,7 +38,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.NewDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForNewDelegationContract(sender.address, options); + const transaction = await this.factory.createTransactionForNewDelegationContract(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -58,7 +64,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.AddNodesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForAddingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForAddingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -75,7 +81,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageNodesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForRemovingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForRemovingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -92,7 +98,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageNodesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForStakingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForStakingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -109,7 +115,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageNodesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnbondingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForUnbondingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -126,7 +132,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageNodesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnstakingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForUnstakingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -143,7 +149,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.UnjailingNodesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnjailingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForUnjailingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -160,7 +166,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ChangeServiceFee & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForChangingServiceFee(sender.address, options); + const transaction = await this.factory.createTransactionForChangingServiceFee(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -177,7 +183,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ModifyDelegationCapInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForModifyingDelegationCap(sender.address, options); + const transaction = await this.factory.createTransactionForModifyingDelegationCap(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -194,7 +200,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingAutomaticActivation(sender.address, options); + const transaction = await this.factory.createTransactionForSettingAutomaticActivation(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -211,7 +217,10 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingAutomaticActivation(sender.address, options); + const transaction = await this.factory.createTransactionForUnsettingAutomaticActivation( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -228,7 +237,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingCapCheckOnRedelegateRewards( + const transaction = await this.factory.createTransactionForSettingCapCheckOnRedelegateRewards( sender.address, options, ); @@ -248,7 +257,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingCapCheckOnRedelegateRewards( + const transaction = await this.factory.createTransactionForUnsettingCapCheckOnRedelegateRewards( sender.address, options, ); @@ -268,7 +277,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.SetContractMetadataInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingMetadata(sender.address, options); + const transaction = await this.factory.createTransactionForSettingMetadata(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -285,7 +294,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.DelegateActionsInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForDelegating(sender.address, options); + const transaction = await this.factory.createTransactionForDelegating(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -302,7 +311,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForClaimingRewards(sender.address, options); + const transaction = await this.factory.createTransactionForClaimingRewards(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -319,7 +328,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForRedelegatingRewards(sender.address, options); + const transaction = await this.factory.createTransactionForRedelegatingRewards(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -336,7 +345,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.DelegateActionsInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUndelegating(sender.address, options); + const transaction = await this.factory.createTransactionForUndelegating(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -353,7 +362,7 @@ export class DelegationController extends BaseController { nonce: bigint, options: resources.ManageDelegationContractInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForWithdrawing(sender.address, options); + const transaction = await this.factory.createTransactionForWithdrawing(sender.address, options); transaction.nonce = nonce; transaction.guardian = options.guardian ?? Address.empty(); diff --git a/src/delegation/delegationTransactionsFactory.spec.ts b/src/delegation/delegationTransactionsFactory.spec.ts index 2b35d3000..beb522287 100644 --- a/src/delegation/delegationTransactionsFactory.spec.ts +++ b/src/delegation/delegationTransactionsFactory.spec.ts @@ -14,7 +14,7 @@ describe("test delegation transactions factory", function () { const serviceFee = 10n; const value = 1250000000000000000000n; - const transaction = delegationFactory.createTransactionForNewDelegationContract(sender, { + const transaction = await delegationFactory.createTransactionForNewDelegationContract(sender, { totalDelegationCap: delagationCap, serviceFee: serviceFee, amount: value, @@ -53,7 +53,7 @@ describe("test delegation transactions factory", function () { ), }; - const transaction = delegationFactory.createTransactionForAddingNodes(sender, { + const transaction = await delegationFactory.createTransactionForAddingNodes(sender, { delegationContract: delegationContract, publicKeys: [publicKey], signedMessages: [mockMessage.getSignature()], @@ -84,7 +84,7 @@ describe("test delegation transactions factory", function () { ), ); - const transaction = delegationFactory.createTransactionForRemovingNodes(sender, { + const transaction = await delegationFactory.createTransactionForRemovingNodes(sender, { delegationContract: delegationContract, publicKeys: [publicKey], }); @@ -113,7 +113,7 @@ describe("test delegation transactions factory", function () { ), ); - const transaction = delegationFactory.createTransactionForStakingNodes(sender, { + const transaction = await delegationFactory.createTransactionForStakingNodes(sender, { delegationContract: delegationContract, publicKeys: [publicKey], }); @@ -142,7 +142,7 @@ describe("test delegation transactions factory", function () { ), ); - const transaction = delegationFactory.createTransactionForUnbondingNodes(sender, { + const transaction = await delegationFactory.createTransactionForUnbondingNodes(sender, { delegationContract: delegationContract, publicKeys: [publicKey], }); @@ -172,7 +172,7 @@ describe("test delegation transactions factory", function () { ), ); - const transaction = delegationFactory.createTransactionForUnstakingNodes(sender, { + const transaction = await delegationFactory.createTransactionForUnstakingNodes(sender, { delegationContract: delegationContract, publicKeys: [publicKey], }); @@ -202,7 +202,7 @@ describe("test delegation transactions factory", function () { ), ); - const transaction = delegationFactory.createTransactionForUnjailingNodes(sender, { + const transaction = await delegationFactory.createTransactionForUnjailingNodes(sender, { delegationContract: delegationContract, publicKeys: [publicKey], amount: 25000000000000000000n, @@ -227,7 +227,7 @@ describe("test delegation transactions factory", function () { const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); const serviceFee = 10n; - const transaction = delegationFactory.createTransactionForChangingServiceFee(sender, { + const transaction = await delegationFactory.createTransactionForChangingServiceFee(sender, { delegationContract: delegationContract, serviceFee: serviceFee, }); @@ -247,7 +247,7 @@ describe("test delegation transactions factory", function () { const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); const delegationCap = 5000000000000000000000n; - const transaction = delegationFactory.createTransactionForModifyingDelegationCap(sender, { + const transaction = await delegationFactory.createTransactionForModifyingDelegationCap(sender, { delegationContract: delegationContract, delegationCap: delegationCap, }); @@ -266,7 +266,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForSettingAutomaticActivation(sender, { + const transaction = await delegationFactory.createTransactionForSettingAutomaticActivation(sender, { delegationContract: delegationContract, }); @@ -284,7 +284,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForUnsettingAutomaticActivation(sender, { + const transaction = await delegationFactory.createTransactionForUnsettingAutomaticActivation(sender, { delegationContract: delegationContract, }); @@ -302,7 +302,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForSettingCapCheckOnRedelegateRewards(sender, { + const transaction = await delegationFactory.createTransactionForSettingCapCheckOnRedelegateRewards(sender, { delegationContract: delegationContract, }); @@ -320,7 +320,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForUnsettingCapCheckOnRedelegateRewards(sender, { + const transaction = await delegationFactory.createTransactionForUnsettingCapCheckOnRedelegateRewards(sender, { delegationContract: delegationContract, }); @@ -338,7 +338,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForSettingMetadata(sender, { + const transaction = await delegationFactory.createTransactionForSettingMetadata(sender, { delegationContract: delegationContract, name: "name", website: "website", @@ -359,7 +359,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForDelegating(sender, { + const transaction = await delegationFactory.createTransactionForDelegating(sender, { delegationContract: delegationContract, amount: 1000000000000000000n, }); @@ -378,7 +378,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForClaimingRewards(sender, { + const transaction = await delegationFactory.createTransactionForClaimingRewards(sender, { delegationContract: delegationContract, }); @@ -395,7 +395,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForRedelegatingRewards(sender, { + const transaction = await delegationFactory.createTransactionForRedelegatingRewards(sender, { delegationContract: delegationContract, }); @@ -412,7 +412,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForUndelegating(sender, { + const transaction = await delegationFactory.createTransactionForUndelegating(sender, { delegationContract: delegationContract, amount: 1000000000000000000n, }); @@ -431,7 +431,7 @@ describe("test delegation transactions factory", function () { const sender = Address.newFromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX); - const transaction = delegationFactory.createTransactionForWithdrawing(sender, { + const transaction = await delegationFactory.createTransactionForWithdrawing(sender, { delegationContract: delegationContract, }); diff --git a/src/delegation/delegationTransactionsFactory.ts b/src/delegation/delegationTransactionsFactory.ts index bfd2d5b3d..4e06d693e 100644 --- a/src/delegation/delegationTransactionsFactory.ts +++ b/src/delegation/delegationTransactionsFactory.ts @@ -1,9 +1,10 @@ import { ArgSerializer, BigUIntValue, BytesValue, StringValue } from "../abi"; +import { IGasLimitEstimator } from "../core"; import { Address } from "../core/address"; +import { BaseFactory } from "../core/baseFactory"; import { DELEGATION_MANAGER_SC_ADDRESS_HEX } from "../core/constants"; import { Err } from "../core/errors"; import { Transaction } from "../core/transaction"; -import { TransactionBuilder } from "../core/transactionBuilder"; import * as resources from "./resources"; interface IConfig { @@ -23,21 +24,22 @@ interface IConfig { /** * Use this class to create delegation related transactions like creating a new delegation contract or adding nodes. */ -export class DelegationTransactionsFactory { +export class DelegationTransactionsFactory extends BaseFactory { private readonly config: IConfig; private readonly argSerializer: ArgSerializer; private readonly delegationManagerAddress: Address; - constructor(options: { config: IConfig }) { + constructor(options: { config: IConfig; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); this.config = options.config; this.argSerializer = new ArgSerializer(); this.delegationManagerAddress = Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX, this.config.addressHrp); } - createTransactionForNewDelegationContract( + async createTransactionForNewDelegationContract( sender: Address, options: resources.NewDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = [ "createNewDelegationContract", ...this.argSerializer.valuesToStrings([ @@ -49,18 +51,21 @@ export class DelegationTransactionsFactory { const executionGasLimit = this.config.gasLimitCreateDelegationContract + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.delegationManagerAddress, - dataParts: dataParts, - gasLimit: executionGasLimit, - addDataMovementGas: true, - amount: options.amount, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: options.amount, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForAddingNodes(sender: Address, options: resources.AddNodesInput): Transaction { + async createTransactionForAddingNodes(sender: Address, options: resources.AddNodesInput): Promise { if (options.publicKeys.length !== options.signedMessages.length) { throw new Err("The number of public keys should match the number of signed messages"); } @@ -77,35 +82,46 @@ export class DelegationTransactionsFactory { dataParts.push(...[options.publicKeys[i].hex(), messagesAsStrings[i]]); } - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + const executionGasLimit = this.computeExecutionGasLimitForNodesManagement(numNodes); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForRemovingNodes(sender: Address, options: resources.ManageNodesInput): Transaction { + async createTransactionForRemovingNodes( + sender: Address, + options: resources.ManageNodesInput, + ): Promise { const dataParts = ["removeNodes"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); } - const numNodes = options.publicKeys.length; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + const numNodes = options.publicKeys.length; + const executionGasLimit = this.computeExecutionGasLimitForNodesManagement(numNodes); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForStakingNodes(sender: Address, options: resources.ManageNodesInput): Transaction { + async createTransactionForStakingNodes(sender: Address, options: resources.ManageNodesInput): Promise { let dataParts = ["stakeNodes"]; for (const key of options.publicKeys) { @@ -118,17 +134,23 @@ export class DelegationTransactionsFactory { const executionGasLimit = additionalGasForAllNodes + this.config.gasLimitStake + this.config.gasLimitDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: executionGasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForUnbondingNodes(sender: Address, options: resources.ManageNodesInput): Transaction { + async createTransactionForUnbondingNodes( + sender: Address, + options: resources.ManageNodesInput, + ): Promise { let dataParts = ["unBondNodes"]; for (const key of options.publicKeys) { @@ -141,17 +163,23 @@ export class DelegationTransactionsFactory { this.config.gasLimitUnbond + this.config.gasLimitDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: executionGasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForUnstakingNodes(sender: Address, options: resources.ManageNodesInput): Transaction { + async createTransactionForUnstakingNodes( + sender: Address, + options: resources.ManageNodesInput, + ): Promise { let dataParts = ["unStakeNodes"]; for (const key of options.publicKeys) { @@ -164,36 +192,49 @@ export class DelegationTransactionsFactory { this.config.gasLimitUnstake + this.config.gasLimitDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: executionGasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForUnjailingNodes(sender: Address, options: resources.UnjailingNodesInput): Transaction { + async createTransactionForUnjailingNodes( + sender: Address, + options: resources.UnjailingNodesInput, + ): Promise { const dataParts = ["unJailNodes"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); } - const numNodes = options.publicKeys.length; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), - addDataMovementGas: true, - amount: options.amount, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: options.amount, + }); + + this.setTransactionPayload(transaction, dataParts); + const numNodes = options.publicKeys.length; + const executionGasLimit = this.computeExecutionGasLimitForNodesManagement(numNodes); + await this.setGasLimit(transaction, undefined, executionGasLimit); + + return transaction; } - createTransactionForChangingServiceFee(sender: Address, options: resources.ChangeServiceFee): Transaction { + async createTransactionForChangingServiceFee( + sender: Address, + options: resources.ChangeServiceFee, + ): Promise { const dataParts = [ "changeServiceFee", this.argSerializer.valuesToStrings([new BigUIntValue(options.serviceFee)])[0], @@ -201,20 +242,23 @@ export class DelegationTransactionsFactory { const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForModifyingDelegationCap( + async createTransactionForModifyingDelegationCap( sender: Address, options: resources.ModifyDelegationCapInput, - ): Transaction { + ): Promise { const dataParts = [ "modifyTotalDelegationCap", this.argSerializer.valuesToStrings([new BigUIntValue(options.delegationCap)])[0], @@ -222,56 +266,65 @@ export class DelegationTransactionsFactory { const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForSettingAutomaticActivation( + async createTransactionForSettingAutomaticActivation( sender: Address, options: resources.ManageDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = ["setAutomaticActivation", this.argSerializer.valuesToStrings([new StringValue("true")])[0]]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForUnsettingAutomaticActivation( + async createTransactionForUnsettingAutomaticActivation( sender: Address, options: resources.ManageDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = ["setAutomaticActivation", this.argSerializer.valuesToStrings([new StringValue("false")])[0]]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForSettingCapCheckOnRedelegateRewards( + async createTransactionForSettingCapCheckOnRedelegateRewards( sender: Address, options: resources.ManageDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = [ "setCheckCapOnReDelegateRewards", this.argSerializer.valuesToStrings([new StringValue("true")])[0], @@ -279,20 +332,23 @@ export class DelegationTransactionsFactory { const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForUnsettingCapCheckOnRedelegateRewards( + async createTransactionForUnsettingCapCheckOnRedelegateRewards( sender: Address, options: resources.ManageDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = [ "setCheckCapOnReDelegateRewards", this.argSerializer.valuesToStrings([new StringValue("false")])[0], @@ -300,17 +356,23 @@ export class DelegationTransactionsFactory { const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForSettingMetadata(sender: Address, options: resources.SetContractMetadataInput): Transaction { + async createTransactionForSettingMetadata( + sender: Address, + options: resources.SetContractMetadataInput, + ): Promise { const dataParts = [ "setMetaData", ...this.argSerializer.valuesToStrings([ @@ -323,101 +385,127 @@ export class DelegationTransactionsFactory { const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForDelegating(sender: Address, options: resources.DelegateActionsInput): Transaction { + async createTransactionForDelegating( + sender: Address, + options: resources.DelegateActionsInput, + ): Promise { const dataParts = ["delegate"]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - amount: options.amount, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: options.amount, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForClaimingRewards( + async createTransactionForClaimingRewards( sender: Address, options: resources.ManageDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = ["claimRewards"]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForRedelegatingRewards( + async createTransactionForRedelegatingRewards( sender: Address, options: resources.ManageDelegationContractInput, - ): Transaction { + ): Promise { const dataParts = ["reDelegateRewards"]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForUndelegating(sender: Address, options: resources.DelegateActionsInput): Transaction { + async createTransactionForUndelegating( + sender: Address, + options: resources.DelegateActionsInput, + ): Promise { const dataParts = ["unDelegate", this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0]]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForWithdrawing(sender: Address, options: resources.ManageDelegationContractInput): Transaction { + async createTransactionForWithdrawing( + sender: Address, + options: resources.ManageDelegationContractInput, + ): Promise { const dataParts = ["withdraw"]; const gasLimit = this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.delegationContract, - dataParts: dataParts, - gasLimit: gasLimit, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } private computeExecutionGasLimitForNodesManagement(numNodes: number): bigint { const additionalGasForAllNodes = this.config.additionalGasLimitPerValidatorNode * BigInt(numNodes); - return this.config.gasLimitDelegationOperations + additionalGasForAllNodes; } } diff --git a/src/entrypoints/entrypoints.spec.ts b/src/entrypoints/entrypoints.spec.ts index 5febc135a..27c726dec 100644 --- a/src/entrypoints/entrypoints.spec.ts +++ b/src/entrypoints/entrypoints.spec.ts @@ -134,4 +134,18 @@ describe("TestEntrypoint", function () { assert.equal(account.secretKey.valueOf().length, 32); assert.equal(account.publicKey.valueOf().length, 32); }); + + it("should estimate gas limit", async () => { + const entrypoint = new DevnetEntrypoint({ withGasLimitEstimator: true, gasLimitMultiplier: 1.5 }); + const controller = entrypoint.createTransfersController(); + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const sender = await Account.newFromPem(filePath); + sender.nonce = await entrypoint.recallAccountNonce(sender.address); + + const transaction = await controller.createTransactionForTransfer(sender, sender.getNonceThenIncrement(), { + receiver: sender.address, + nativeAmount: 10000000n, + }); + assert.equal(transaction.gasLimit, 75000n); + }); }); diff --git a/src/entrypoints/entrypoints.ts b/src/entrypoints/entrypoints.ts index 2498d95f9..07db65d4b 100644 --- a/src/entrypoints/entrypoints.ts +++ b/src/entrypoints/entrypoints.ts @@ -12,6 +12,7 @@ import { TransactionWatcher, } from "../core"; import { DelegationController, DelegationTransactionsFactory } from "../delegation"; +import { GasLimitEstimator } from "../gasEstimator"; import { GovernanceController, GovernanceTransactionsFactory } from "../governance"; import { MultisigTransactionsFactory } from "../multisig"; import { MultisigController } from "../multisig/multisigController"; @@ -27,12 +28,16 @@ import { DevnetEntrypointConfig, MainnetEntrypointConfig, TestnetEntrypointConfi export class NetworkEntrypoint { private networkProvider: INetworkProvider; private chainId: string; + private withGasLimitEstimator?: boolean; + private gasLimitMultiplier?: number; constructor(options: { networkProviderUrl: string; networkProviderKind: string; chainId: string; clientName?: string; + withGasLimitEstimator?: boolean; + gasLimitMultiplier?: number; }) { if (options.networkProviderKind === "proxy") { this.networkProvider = new ProxyNetworkProvider(options.networkProviderUrl, { @@ -47,6 +52,8 @@ export class NetworkEntrypoint { } this.chainId = options.chainId; + this.withGasLimitEstimator = options.withGasLimitEstimator; + this.gasLimitMultiplier = options.gasLimitMultiplier; } /** @@ -138,107 +145,180 @@ export class NetworkEntrypoint { return this.networkProvider; } + protected createGasLimitEstimator(): GasLimitEstimator { + return new GasLimitEstimator({ + networkProvider: this.networkProvider, + gasMultiplier: this.gasLimitMultiplier, + }); + } + createDelegationController(): DelegationController { - return new DelegationController({ chainID: this.chainId, networkProvider: this.networkProvider }); + return new DelegationController({ + chainID: this.chainId, + networkProvider: this.networkProvider, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createDelegationTransactionsFactory(): DelegationTransactionsFactory { - return new DelegationTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }) }); + return new DelegationTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: this.chainId }), + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createAccountController(): AccountController { - return new AccountController({ chainID: this.chainId }); + return new AccountController({ + chainID: this.chainId, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createAccountTransactionsFactory(): AccountTransactionsFactory { - return new AccountTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }) }); + return new AccountTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: this.chainId }), + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createSmartContractController(abi?: Abi): SmartContractController { - return new SmartContractController({ chainID: this.chainId, networkProvider: this.networkProvider, abi }); + return new SmartContractController({ + chainID: this.chainId, + networkProvider: this.networkProvider, + abi, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createSmartContractTransactionsFactory(abi?: Abi): SmartContractTransactionsFactory { return new SmartContractTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }), abi: abi, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, }); } createTokenManagementController(): TokenManagementController { - return new TokenManagementController({ chainID: this.chainId, networkProvider: this.networkProvider }); + return new TokenManagementController({ + chainID: this.chainId, + networkProvider: this.networkProvider, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createTokenManagementTransactionsFactory(): TokenManagementTransactionsFactory { return new TokenManagementTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }), + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, }); } createTransfersController(): TransfersController { - return new TransfersController({ chainID: this.chainId }); + return new TransfersController({ + chainID: this.chainId, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createTransfersTransactionsFactory(): TransferTransactionsFactory { return new TransferTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }), + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, }); } createMultisigController(abi: Abi): MultisigController { - return new MultisigController({ chainID: this.chainId, networkProvider: this.networkProvider, abi: abi }); + return new MultisigController({ + chainID: this.chainId, + networkProvider: this.networkProvider, + abi: abi, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createMultisigTransactionsFactory(abi: Abi): MultisigTransactionsFactory { return new MultisigTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }), abi: abi, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, }); } createGovernanceController(): GovernanceController { - return new GovernanceController({ chainID: this.chainId, networkProvider: this.networkProvider }); + return new GovernanceController({ + chainID: this.chainId, + networkProvider: this.networkProvider, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); } createGovernanceTransactionsFactory(): GovernanceTransactionsFactory { return new GovernanceTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: this.chainId }), + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, }); } } export class TestnetEntrypoint extends NetworkEntrypoint { - constructor(url?: string, kind?: string, clientName?: string) { + constructor(options?: { + url?: string; + kind?: string; + clientName?: string; + withGasLimitEstimator?: boolean; + gasLimitMultiplier?: number; + }) { const entrypointConfig = new TestnetEntrypointConfig(); + options = options || {}; super({ - networkProviderUrl: url || entrypointConfig.networkProviderUrl, - networkProviderKind: kind || entrypointConfig.networkProviderKind, + networkProviderUrl: options.url || entrypointConfig.networkProviderUrl, + networkProviderKind: options.kind || entrypointConfig.networkProviderKind, chainId: entrypointConfig.chainId, - clientName: clientName, + clientName: options.clientName, + withGasLimitEstimator: options.withGasLimitEstimator, + gasLimitMultiplier: options.gasLimitMultiplier, }); } } export class DevnetEntrypoint extends NetworkEntrypoint { - constructor(url?: string, kind?: string, clientName?: string) { + constructor(options?: { + url?: string; + kind?: string; + clientName?: string; + withGasLimitEstimator?: boolean; + gasLimitMultiplier?: number; + }) { const entrypointConfig = new DevnetEntrypointConfig(); + options = options || {}; super({ - networkProviderUrl: url || entrypointConfig.networkProviderUrl, - networkProviderKind: kind || entrypointConfig.networkProviderKind, + networkProviderUrl: options.url || entrypointConfig.networkProviderUrl, + networkProviderKind: options.kind || entrypointConfig.networkProviderKind, chainId: entrypointConfig.chainId, - clientName: clientName, + clientName: options.clientName, + withGasLimitEstimator: options.withGasLimitEstimator, + gasLimitMultiplier: options.gasLimitMultiplier, }); } } export class MainnetEntrypoint extends NetworkEntrypoint { - constructor(url?: string, kind?: string, clientName?: string) { + constructor(options?: { + url?: string; + kind?: string; + clientName?: string; + withGasLimitEstimator?: boolean; + gasLimitMultiplier?: number; + }) { const entrypointConfig = new MainnetEntrypointConfig(); + options = options || {}; super({ - networkProviderUrl: url || entrypointConfig.networkProviderUrl, - networkProviderKind: kind || entrypointConfig.networkProviderKind, + networkProviderUrl: options.url || entrypointConfig.networkProviderUrl, + networkProviderKind: options.kind || entrypointConfig.networkProviderKind, chainId: entrypointConfig.chainId, - clientName: clientName, + clientName: options.clientName, + withGasLimitEstimator: options.withGasLimitEstimator, + gasLimitMultiplier: options.gasLimitMultiplier, }); } } diff --git a/src/gasEstimator/gasLimitEstimator.spec.ts b/src/gasEstimator/gasLimitEstimator.spec.ts new file mode 100644 index 000000000..62444a2bb --- /dev/null +++ b/src/gasEstimator/gasLimitEstimator.spec.ts @@ -0,0 +1,73 @@ +import { assert } from "chai"; +import { Address, TransactionStatus } from "../core"; +import { Transaction } from "../core/transaction"; +import { MockNetworkProvider } from "../testutils"; +import { GasLimitEstimator } from "./gasLimitEstimator"; + +describe("GasLimitEstimator tests", () => { + it("should estimate gas limit with default multiplier", async () => { + const networkProvider = new MockNetworkProvider(); + const mockTxCostResponse = { + raw: {}, + gasLimit: 50000, + status: TransactionStatus.createUnknown(), + }; + networkProvider.mockTransactionCostResponse = mockTxCostResponse; + + const estimator = new GasLimitEstimator({ networkProvider }); + const tx = new Transaction({ + sender: Address.empty(), + receiver: Address.empty(), + chainID: "D", + gasLimit: 0n, + value: 10000000n, + }); + + const estimatedGas = await estimator.estimateGasLimit({ transaction: tx }); + assert.equal(estimatedGas, 50000n); + }); + + it("should estimate gas limit with multiplier", async () => { + const networkProvider = new MockNetworkProvider(); + const mockTxCostResponse = { + raw: {}, + gasLimit: 50000, + status: TransactionStatus.createUnknown(), + }; + networkProvider.mockTransactionCostResponse = mockTxCostResponse; + + const estimator = new GasLimitEstimator({ networkProvider: networkProvider, gasMultiplier: 1.5 }); + const tx = new Transaction({ + sender: Address.empty(), + receiver: Address.empty(), + chainID: "D", + gasLimit: 0n, + value: 10000000n, + }); + + const estimatedGas = await estimator.estimateGasLimit({ transaction: tx }); + assert.equal(estimatedGas, 75000n); + }); + + it("should round down estimated gas limit with multiplier", async () => { + const networkProvider = new MockNetworkProvider(); + const mockTxCostResponse = { + raw: {}, + gasLimit: 50000, + status: TransactionStatus.createUnknown(), + }; + networkProvider.mockTransactionCostResponse = mockTxCostResponse; + + const estimator = new GasLimitEstimator({ networkProvider: networkProvider, gasMultiplier: 1.98765 }); + const tx = new Transaction({ + sender: Address.empty(), + receiver: Address.empty(), + chainID: "D", + gasLimit: 0n, + value: 10000000n, + }); + + const estimatedGas = await estimator.estimateGasLimit({ transaction: tx }); + assert.equal(estimatedGas, 99382n); + }); +}); diff --git a/src/gasEstimator/gasLimitEstimator.ts b/src/gasEstimator/gasLimitEstimator.ts new file mode 100644 index 000000000..d38cfc2e2 --- /dev/null +++ b/src/gasEstimator/gasLimitEstimator.ts @@ -0,0 +1,29 @@ +import { ErrGasLimitCannotBeEstimated, Transaction } from "../core"; + +interface ITransactionCostResponse { + gasLimit: number; +} + +interface INetworkProvider { + estimateTransactionCost(tx: Transaction): Promise; +} + +export class GasLimitEstimator { + private networkProvider: INetworkProvider; + private gasMultiplier: number; + + constructor(options: { networkProvider: INetworkProvider; gasMultiplier?: number }) { + this.networkProvider = options.networkProvider; + this.gasMultiplier = options.gasMultiplier || 1.0; + } + + async estimateGasLimit(options: { transaction: Transaction }): Promise { + try { + const gasLimit = (await this.networkProvider.estimateTransactionCost(options.transaction)).gasLimit; + const multipliedEstimatedGas = Math.floor(gasLimit * this.gasMultiplier); + return BigInt(multipliedEstimatedGas); + } catch (error: any) { + throw new ErrGasLimitCannotBeEstimated(error); + } + } +} diff --git a/src/gasEstimator/index.ts b/src/gasEstimator/index.ts new file mode 100644 index 000000000..581a394dd --- /dev/null +++ b/src/gasEstimator/index.ts @@ -0,0 +1 @@ +export { GasLimitEstimator } from "./gasLimitEstimator"; diff --git a/src/governance/governanceController.spec.ts b/src/governance/governanceController.spec.ts index 997fefa99..a2240afa4 100644 --- a/src/governance/governanceController.spec.ts +++ b/src/governance/governanceController.spec.ts @@ -173,6 +173,7 @@ describe("test governance controller", function () { const config = await controller.getConfig(); assert.equal(config.proposalFee, 1000_000000000000000000n); + assert.equal(config.lostProposalFee, 10_000000000000000000n); assert.equal(config.minQuorum, 0.2); assert.equal(config.minPassThreshold, 0.5); assert.equal(config.minVetoThreshold, 0.33); diff --git a/src/governance/governanceController.ts b/src/governance/governanceController.ts index c5ba39762..914ec4511 100644 --- a/src/governance/governanceController.ts +++ b/src/governance/governanceController.ts @@ -4,6 +4,7 @@ import { BaseController, BaseControllerInput, IAccount, + IGasLimitEstimator, LibraryConfig, Transaction, TransactionOnNetwork, @@ -38,10 +39,16 @@ export class GovernanceController extends BaseController { private readonly addressHrp: string; private readonly serializer: ArgSerializer; - constructor(options: { chainID: string; networkProvider: INetworkProvider; addressHrp?: string }) { + constructor(options: { + chainID: string; + networkProvider: INetworkProvider; + addressHrp?: string; + gasLimitEstimator?: IGasLimitEstimator; + }) { super(); this.governanceFactory = new GovernanceTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), + gasLimitEstimator: options.gasLimitEstimator, }); this.smartContractController = new SmartContractController({ chainID: options.chainID, @@ -59,7 +66,7 @@ export class GovernanceController extends BaseController { nonce: bigint, options: NewProposalInput & BaseControllerInput, ): Promise { - const transaction = this.governanceFactory.createTransactionForNewProposal(sender.address, options); + const transaction = await this.governanceFactory.createTransactionForNewProposal(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -85,7 +92,7 @@ export class GovernanceController extends BaseController { nonce: bigint, options: VoteProposalInput & BaseControllerInput, ): Promise { - const transaction = this.governanceFactory.createTransactionForVoting(sender.address, options); + const transaction = await this.governanceFactory.createTransactionForVoting(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -111,7 +118,7 @@ export class GovernanceController extends BaseController { nonce: bigint, options: CloseProposalInput & BaseControllerInput, ): Promise { - const transaction = this.governanceFactory.createTransactionForClosingProposal(sender.address, options); + const transaction = await this.governanceFactory.createTransactionForClosingProposal(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -137,7 +144,10 @@ export class GovernanceController extends BaseController { nonce: bigint, options: ClearEndedProposalsInput & BaseControllerInput, ): Promise { - const transaction = this.governanceFactory.createTransactionForClearingEndedProposals(sender.address, options); + const transaction = await this.governanceFactory.createTransactionForClearingEndedProposals( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -154,7 +164,7 @@ export class GovernanceController extends BaseController { nonce: bigint, options: BaseControllerInput, ): Promise { - const transaction = this.governanceFactory.createTransactionForClaimingAccumulatedFees(sender.address); + const transaction = await this.governanceFactory.createTransactionForClaimingAccumulatedFees(sender.address); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -171,7 +181,7 @@ export class GovernanceController extends BaseController { nonce: bigint, options: ChangeConfigInput & BaseControllerInput, ): Promise { - const transaction = this.governanceFactory.createTransactionForChangingConfig(sender.address, options); + const transaction = await this.governanceFactory.createTransactionForChangingConfig(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); diff --git a/src/governance/governanceTransactionsFactory.spec.ts b/src/governance/governanceTransactionsFactory.spec.ts index bdc9911fc..5e5373e1a 100644 --- a/src/governance/governanceTransactionsFactory.spec.ts +++ b/src/governance/governanceTransactionsFactory.spec.ts @@ -14,10 +14,10 @@ describe("test governance transactions factory", function () { const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const governanceAddress = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla"; - it("should create transaction for creating new proposal", function () { + it("should create transaction for creating new proposal", async function () { const expectedData = `proposal@${Buffer.from(commitHash).toString("hex")}@0a@0f`; - const transaction = factory.createTransactionForNewProposal(alice, { + const transaction = await factory.createTransactionForNewProposal(alice, { commitHash: commitHash, startVoteEpoch: 10, endVoteEpoch: 15, @@ -32,8 +32,8 @@ describe("test governance transactions factory", function () { assert.equal(transaction.data.toString(), expectedData); }); - it("should create transaction for voting", function () { - const transaction = factory.createTransactionForVoting(alice, { + it("should create transaction for voting", async function () { + const transaction = await factory.createTransactionForVoting(alice, { proposalNonce: 1, vote: Vote.YES, }); @@ -46,8 +46,8 @@ describe("test governance transactions factory", function () { assert.equal(transaction.data.toString(), "vote@01@796573"); }); - it("should create transaction for closing proposal", function () { - const transaction = factory.createTransactionForClosingProposal(alice, { + it("should create transaction for closing proposal", async function () { + const transaction = await factory.createTransactionForClosingProposal(alice, { proposalNonce: 1, }); @@ -59,11 +59,11 @@ describe("test governance transactions factory", function () { assert.equal(transaction.data.toString(), "closeProposal@01"); }); - it("should create transaction for clearing ended proposals", function () { + it("should create transaction for clearing ended proposals", async function () { const expectedData = "clearEndedProposals@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; - const transaction = factory.createTransactionForClearingEndedProposals(alice, { + const transaction = await factory.createTransactionForClearingEndedProposals(alice, { proposers: [alice, Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")], }); @@ -75,8 +75,8 @@ describe("test governance transactions factory", function () { assert.equal(transaction.data.toString(), expectedData); }); - it("should create transaction for claiming accumulated fees", function () { - const transaction = factory.createTransactionForClaimingAccumulatedFees(alice); + it("should create transaction for claiming accumulated fees", async function () { + const transaction = await factory.createTransactionForClaimingAccumulatedFees(alice); assert.equal(transaction.sender.toBech32(), alice.toBech32()); assert.equal(transaction.receiver.toBech32(), governanceAddress); @@ -86,11 +86,11 @@ describe("test governance transactions factory", function () { assert.equal(transaction.data.toString(), "claimAccumulatedFees"); }); - it("should create transaction for changing config", function () { + it("should create transaction for changing config", async function () { const expectedData = "changeConfig@31303030303030303030303030303030303030303030@3130303030303030303030303030303030303030@35303030@33303030@36303030"; - const transaction = factory.createTransactionForChangingConfig(alice, { + const transaction = await factory.createTransactionForChangingConfig(alice, { proposalFee: 1000000000000000000000n, lastProposalFee: 10000000000000000000n, minQuorum: 5000, diff --git a/src/governance/governanceTransactionsFactory.ts b/src/governance/governanceTransactionsFactory.ts index 7eb32e2bb..16e84546d 100644 --- a/src/governance/governanceTransactionsFactory.ts +++ b/src/governance/governanceTransactionsFactory.ts @@ -1,7 +1,7 @@ import { ArgSerializer, BigUIntValue, StringValue } from "../abi"; -import { Address, Transaction, TransactionsFactoryConfig } from "../core"; +import { Address, IGasLimitEstimator, Transaction, TransactionsFactoryConfig } from "../core"; +import { BaseFactory } from "../core/baseFactory"; import { GOVERNANCE_CONTRACT_ADDRESS_HEX } from "../core/constants"; -import { TransactionBuilder } from "../core/transactionBuilder"; import { ChangeConfigInput, ClearEndedProposalsInput, @@ -25,18 +25,19 @@ interface IConfig { const EXTRA_GAS_LIMIT_FOR_VOTING = 100_000n; -export class GovernanceTransactionsFactory { +export class GovernanceTransactionsFactory extends BaseFactory { private readonly config: IConfig; private readonly argSerializer: ArgSerializer; private readonly governanceContract: Address; - constructor(options: { config: TransactionsFactoryConfig }) { + constructor(options: { config: TransactionsFactoryConfig; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); 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 { + async createTransactionForNewProposal(sender: Address, options: NewProposalInput): Promise { const args = [ new StringValue(options.commitHash), new BigUIntValue(options.startVoteEpoch), @@ -44,74 +45,94 @@ export class GovernanceTransactionsFactory { ]; const dataParts = ["proposal", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.governanceContract, - dataParts: dataParts, - gasLimit: this.config.gasLimitForProposal, - addDataMovementGas: true, - amount: options.nativeTokenAmount, - }).build(); + value: options.nativeTokenAmount, + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForProposal); + + return transaction; } - createTransactionForVoting(sender: Address, options: VoteProposalInput): Transaction { + async createTransactionForVoting(sender: Address, options: VoteProposalInput): Promise { 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, + const transaction = new Transaction({ + sender, receiver: this.governanceContract, - dataParts: dataParts, - gasLimit: this.config.gasLimitForVote + EXTRA_GAS_LIMIT_FOR_VOTING, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForVote + EXTRA_GAS_LIMIT_FOR_VOTING); + + return transaction; } - createTransactionForClosingProposal(sender: Address, options: CloseProposalInput): Transaction { + async createTransactionForClosingProposal(sender: Address, options: CloseProposalInput): Promise { const args = [new BigUIntValue(options.proposalNonce)]; const dataParts = ["closeProposal", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.governanceContract, - dataParts: dataParts, - gasLimit: this.config.gasLimitForClosingProposal, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForClosingProposal); + + return transaction; } - createTransactionForClearingEndedProposals(sender: Address, options: ClearEndedProposalsInput): Transaction { + async createTransactionForClearingEndedProposals( + sender: Address, + options: ClearEndedProposalsInput, + ): Promise { const dataParts = ["clearEndedProposals", ...options.proposers.map((address) => address.toHex())]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.governanceContract, - dataParts: dataParts, - gasLimit: - this.config.gasLimitForClearProposals + - BigInt(options.proposers.length) * this.config.gasLimitForClearProposals, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + + const gasLimit = + this.config.gasLimitForClearProposals + + BigInt(options.proposers.length) * this.config.gasLimitForClearProposals; + await this.setGasLimit(transaction, undefined, gasLimit); + + return transaction; } - createTransactionForClaimingAccumulatedFees(sender: Address): Transaction { + async createTransactionForClaimingAccumulatedFees(sender: Address): Promise { const dataParts = ["claimAccumulatedFees"]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.governanceContract, - dataParts: dataParts, - gasLimit: this.config.gasLimitForClaimAccumulatedFees, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForClaimAccumulatedFees); + + return transaction; } - createTransactionForChangingConfig(sender: Address, options: ChangeConfigInput): Transaction { + async createTransactionForChangingConfig(sender: Address, options: ChangeConfigInput): Promise { const args = [ new StringValue(options.proposalFee.toString()), new StringValue(options.lastProposalFee.toString()), @@ -121,13 +142,16 @@ export class GovernanceTransactionsFactory { ]; const dataParts = ["changeConfig", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.governanceContract, - dataParts: dataParts, - gasLimit: this.config.gasLimitForChangeConfig, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForChangeConfig); + + return transaction; } } diff --git a/src/index.ts b/src/index.ts index 74f3941d6..152af6dad 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 "./gasEstimator"; export * from "./governance"; export * from "./multisig"; export * from "./networkProviders"; diff --git a/src/multisig/index.ts b/src/multisig/index.ts index 7af23fc96..f5aa16bb6 100644 --- a/src/multisig/index.ts +++ b/src/multisig/index.ts @@ -1,3 +1,5 @@ +export * from "./multisigController"; export * from "./multisigTransactionsFactory"; +export * from "./multisigTransactionsOutcomeParser"; export * from "./proposeTransferExecuteContractInput"; export * from "./resources"; diff --git a/src/multisig/multisigController.ts b/src/multisig/multisigController.ts index 5d421707d..6a78adf6b 100644 --- a/src/multisig/multisigController.ts +++ b/src/multisig/multisigController.ts @@ -5,6 +5,7 @@ import { BaseController, BaseControllerInput, IAccount, + IGasLimitEstimator, Transaction, TransactionOnNetwork, TransactionsFactoryConfig, @@ -22,12 +23,18 @@ export class MultisigController extends BaseController { private multisigParser: MultisigTransactionsOutcomeParser; private smartContractController: SmartContractController; - constructor(options: { chainID: string; networkProvider: INetworkProvider; abi: Abi }) { + constructor(options: { + chainID: string; + networkProvider: INetworkProvider; + abi: Abi; + gasLimitEstimator?: IGasLimitEstimator; + }) { super(); this.transactionAwaiter = new TransactionWatcher(options.networkProvider); this.multisigFactory = new MultisigTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), abi: options.abi, + gasLimitEstimator: options.gasLimitEstimator, }); this.multisigParser = new MultisigTransactionsOutcomeParser({ abi: options.abi }); this.smartContractController = new SmartContractController({ @@ -45,7 +52,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.DeployMultisigContractInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForDeploy(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForDeploy(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -312,7 +319,10 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeAddBoardMemberInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeAddBoardMember(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForProposeAddBoardMember( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -331,7 +341,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeAddProposerInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeAddProposer(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForProposeAddProposer(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -350,7 +360,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeRemoveUserInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeRemoveUser(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForProposeRemoveUser(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -369,7 +379,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeChangeQuorumInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeChangeQuorum(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForProposeChangeQuorum(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -397,7 +407,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ActionInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForSignAction(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForSignAction(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -416,7 +426,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ActionInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForPerformAction(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForPerformAction(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -444,7 +454,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ActionInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForUnsign(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForUnsign(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -463,7 +473,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ActionInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForDiscardAction(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForDiscardAction(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -482,7 +492,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.DepositExecuteInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForDeposit(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForDeposit(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -501,7 +511,10 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeTransferExecuteInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeTransferExecute(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForProposeTransferExecute( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -520,7 +533,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeTransferExecuteEsdtInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeTransferExecuteEsdt( + const transaction = await this.multisigFactory.createTransactionForProposeTransferExecuteEsdt( sender.address, options, ); @@ -542,7 +555,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeAsyncCallInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeAsyncCall(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForProposeAsyncCall(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -561,7 +574,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeContractDeployFromSourceInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeContractDeployFromSource( + const transaction = await this.multisigFactory.createTransactionForProposeContractDeployFromSource( sender.address, options, ); @@ -583,7 +596,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ProposeContractUpgradeFromSourceInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForProposeContractUpgradeFromSource( + const transaction = await this.multisigFactory.createTransactionForProposeContractUpgradeFromSource( sender.address, options, ); @@ -605,7 +618,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.GroupInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForSignBatch(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForSignBatch(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -624,7 +637,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.ActionInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForSignAndPerform(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForSignAndPerform(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -643,7 +656,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.UnsignForOutdatedBoardMembersInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForUnsignForOutdatedBoardMembers( + const transaction = await this.multisigFactory.createTransactionForUnsignForOutdatedBoardMembers( sender.address, options, ); @@ -665,7 +678,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.GroupInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForPerformBatch(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForPerformBatch(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -684,7 +697,7 @@ export class MultisigController extends BaseController { nonce: bigint, options: resources.DiscardBatchInput & BaseControllerInput, ): Promise { - const transaction = this.multisigFactory.createTransactionForDiscardBatch(sender.address, options); + const transaction = await this.multisigFactory.createTransactionForDiscardBatch(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); diff --git a/src/multisig/multisigTransactionsFactory.spec.ts b/src/multisig/multisigTransactionsFactory.spec.ts index d93e7aeee..d40533461 100644 --- a/src/multisig/multisigTransactionsFactory.spec.ts +++ b/src/multisig/multisigTransactionsFactory.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Abi, AddressValue, BigUIntValue, Code, U32Value, VariadicValue } from "../abi"; +import { Abi, AddressValue, BigUIntValue, U32Value, VariadicValue } from "../abi"; import { CodeMetadata, Token, TokenTransfer } from "../core"; import { Address } from "../core/address"; import { Transaction } from "../core/transaction"; @@ -12,7 +12,7 @@ describe("test multisig transactions factory", function () { chainID: "D", }); - let bytecode: Code; + let bytecode: Uint8Array; let abi: Abi; let adderAbi: Abi; let esdtSafeAbi: Abi; @@ -29,7 +29,7 @@ describe("test multisig transactions factory", function () { }); }); - it("should create transaction for deploy multisig contract", function () { + it("should create transaction for deploy multisig contract", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const boardMemberOne = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -37,12 +37,13 @@ describe("test multisig transactions factory", function () { const board = [boardMemberOne, boardMemberTwo]; - const transaction = factory.createTransactionForDeploy(senderAddress, { - bytecode: bytecode.valueOf(), + const transaction = await factory.createTransactionForDeploy(senderAddress, { + bytecode: bytecode, gasLimit: 5000000n, quorum: 2, board, }); + const bytecodeHex = Buffer.from(bytecode).toString("hex"); assert.instanceOf(transaction, Transaction); assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"); @@ -51,19 +52,19 @@ describe("test multisig transactions factory", function () { assert.deepEqual( Buffer.from(transaction.data), Buffer.from( - `${bytecode}@0500@0504@02@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba`, + `${bytecodeHex}@0500@0504@02@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba`, ), ); }); - it("should create transaction for propose add board member", function () { + it("should create transaction for propose add board member", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const boardMember = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForProposeAddBoardMember(senderAddress, { + const transaction = await factory.createTransactionForProposeAddBoardMember(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, boardMember: boardMember, @@ -79,14 +80,14 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose add proposer", function () { + it("should create transaction for propose add proposer", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const proposer = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForProposeAddProposer(senderAddress, { + const transaction = await factory.createTransactionForProposeAddProposer(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, proposer: proposer, @@ -102,14 +103,14 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose remove user", function () { + it("should create transaction for propose remove user", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const userAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForProposeRemoveUser(senderAddress, { + const transaction = await factory.createTransactionForProposeRemoveUser(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, userAddress: userAddress, @@ -125,13 +126,13 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose change quorum", function () { + it("should create transaction for propose change quorum", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForProposeChangeQuorum(senderAddress, { + const transaction = await factory.createTransactionForProposeChangeQuorum(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, newQuorum: 3, @@ -144,7 +145,7 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "proposeChangeQuorum@03"); }); - it("should create transaction for propose transfer execute", function () { + it("should create transaction for propose transfer execute", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const destinationContract = Address.newFromBech32( "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms", @@ -153,7 +154,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq6kurkz43xq8t35kx9p8rvyz5kpxe9g7qd8ssefqjw8", ); const amount = 1000000000000000000n; // 1 EGLD - const transaction = factory.createTransactionForProposeTransferExecute(senderAddress, { + const transaction = await factory.createTransactionForProposeTransferExecute(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, nativeTokenAmount: amount, @@ -168,20 +169,21 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 5000000n); assert.deepEqual( transaction.data.toString(), "proposeTransferExecute@0000000000000000050078d29632acb15998003f615d0a51261353d8041d3e13@0de0b6b3a7640000@0100000000004c4b40@616464@07", ); }); - it("should create transaction for propose transfer execute with EGLD send", function () { + it("should create transaction for propose transfer execute with EGLD send", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms", ); const amount = 1000000000000000000n; // 1 EGLD - const transaction = factory.createTransactionForProposeTransferExecute(senderAddress, { + const transaction = await factory.createTransactionForProposeTransferExecute(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 60_000_000n, nativeTokenAmount: amount, @@ -198,7 +200,31 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose transfer execute ESDT", function () { + it("should create transaction for propose transfer execute with EGLD send", async function () { + const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const multisigContractAddress = Address.newFromBech32( + "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms", + ); + const amount = 1000000000000000000n; // 1 EGLD + const transaction = await factory.createTransactionForProposeTransferExecute(senderAddress, { + multisigContract: multisigContractAddress, + gasLimit: 60_000_000n, + nativeTokenAmount: amount, + to: multisigContractAddress, + }); + + 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(), + "proposeTransferExecute@0000000000000000050078d29632acb15998003f615d0a51261353d8041d3e13@0de0b6b3a7640000@", + ); + }); + + it("should create transaction for propose transfer execute ESDT", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const destinationContract = Address.newFromBech32( "erd1qqqqqqqqqqqqqpgqfxlljcaalgl2qfcnxcsftheju0ts36kvl3ts3qkewe", @@ -212,7 +238,7 @@ describe("test multisig transactions factory", function () { }); const tokenTransfer = new TokenTransfer({ token: token, amount: 10n }); - const transaction = factory.createTransactionForProposeTransferExecuteEsdt(senderAddress, { + const transaction = await factory.createTransactionForProposeTransferExecuteEsdt(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, to: destinationContract, @@ -226,13 +252,14 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 5000000n); assert.deepEqual( transaction.data.toString(), "proposeTransferExecuteEsdt@0000000000000000050049bff963bdfa3ea02713362095df32e3d708eaccfc57@0000000c414c4943452d3536323766310000000000000000000000010a@0100000000004c4b40@3634363937333734373236393632373537343635", ); }); - it("should create transaction for propose async call", function () { + it("should create transaction for propose async call", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const destinationContract = Address.newFromBech32( "erd1qqqqqqqqqqqqqpgq0rffvv4vk9vesqplv9ws55fxzdfaspqa8cfszy2hms", @@ -240,7 +267,7 @@ describe("test multisig transactions factory", function () { const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqpgq6kurkz43xq8t35kx9p8rvyz5kpxe9g7qd8ssefqjw8", ); - const transaction = factory.createTransactionForProposeAsyncCall(senderAddress, { + const transaction = await factory.createTransactionForProposeAsyncCall(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, nativeTransferAmount: 0n, @@ -256,20 +283,21 @@ describe("test multisig transactions factory", function () { assert.equal(transaction.sender.toBech32(), senderAddress.toBech32()); assert.equal(transaction.receiver.toBech32(), multisigContractAddress.toBech32()); assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 5000000n); assert.equal( transaction.data.toString(), "proposeAsyncCall@0000000000000000050078d29632acb15998003f615d0a51261353d8041d3e13@@4c4b40@616464@07", ); }); - it("should create transaction for deposit the expected amount of egld", function () { + it("should create transaction for deposit the expected amount of egld", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForDeposit(senderAddress, { + const transaction = await factory.createTransactionForDeposit(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, nativeTokenAmount: 1n, @@ -284,7 +312,7 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "deposit"); }); - it("should create transaction for deposit esdt token", function () { + it("should create transaction for deposit esdt token", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( @@ -295,7 +323,7 @@ describe("test multisig transactions factory", function () { }); const tokenTransfer = new TokenTransfer({ token: token, amount: 100n }); - const transaction = factory.createTransactionForDeposit(senderAddress, { + const transaction = await factory.createTransactionForDeposit(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, nativeTokenAmount: 0n, @@ -310,7 +338,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 when abi is passed", function () { + it("should create transaction for propose SC deploy from source when abi is passed", async function () { const amount = BigInt(50000000000000000); // 0.05 EGLD const metadata = new CodeMetadata(true, true, false); const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -319,7 +347,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", ); - const transaction = factory.createTransactionForProposeContractDeployFromSource(senderAddress, { + const transaction = await factory.createTransactionForProposeContractDeployFromSource(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, amount: amount, @@ -339,7 +367,7 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose SC deploy from source when no abi is passed", function () { + it("should create transaction for propose SC deploy from source when no abi is passed", async function () { const amount = BigInt(50000000000000000); // 0.05 EGLD const metadata = new CodeMetadata(true, true, false); const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -348,7 +376,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", ); - const transaction = factory.createTransactionForProposeContractDeployFromSource(senderAddress, { + const transaction = await factory.createTransactionForProposeContractDeployFromSource(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, amount: amount, @@ -367,7 +395,7 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose SC upgrade from source when abi is passed", function () { + it("should create transaction for propose SC upgrade from source when abi is passed", async function () { const amount = BigInt(50000000000000000); // 0.05 EGLD const metadata = new CodeMetadata(true, true, false); const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -376,7 +404,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", ); - const transaction = factory.createTransactionForProposeContractUpgradeFromSource(senderAddress, { + const transaction = await factory.createTransactionForProposeContractUpgradeFromSource(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, scAddress: multisigContractAddress, @@ -401,7 +429,7 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for propose SC upgrade from source when no abi is passed", function () { + it("should create transaction for propose SC upgrade from source when no abi is passed", async function () { const amount = BigInt(50000000000000000); // 0.05 EGLD const metadata = new CodeMetadata(true, true, false); const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); @@ -410,7 +438,7 @@ describe("test multisig transactions factory", function () { "erd1qqqqqqqqqqqqqpgq0cjuum0t436gmp446wf3yz43avp2gm2czeus8mctaf", ); - const transaction = factory.createTransactionForProposeContractUpgradeFromSource(senderAddress, { + const transaction = await factory.createTransactionForProposeContractUpgradeFromSource(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, scAddress: multisigContractAddress, @@ -440,13 +468,13 @@ describe("test multisig transactions factory", function () { ); }); - it("should create transaction for sign action", function () { + it("should create transaction for sign action", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForSignAction(senderAddress, { + const transaction = await factory.createTransactionForSignAction(senderAddress, { multisigContract: multisigContractAddress, actionId: 42, gasLimit: 5000000n, @@ -459,13 +487,13 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "sign@2a"); }); - it("should create transaction for sign batch", function () { + it("should create transaction for sign batch", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForSignBatch(senderAddress, { + const transaction = await factory.createTransactionForSignBatch(senderAddress, { multisigContract: multisigContractAddress, groupId: 5, gasLimit: 5000000n, @@ -478,13 +506,13 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "signBatch@05"); }); - it("should create transaction for sign and perform", function () { + it("should create transaction for sign and perform", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForSignAndPerform(senderAddress, { + const transaction = await factory.createTransactionForSignAndPerform(senderAddress, { multisigContract: multisigContractAddress, actionId: 42, gasLimit: 5000000n, @@ -497,13 +525,13 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "signAndPerform@2a"); }); - it("should create transaction for unsign", function () { + it("should create transaction for unsign", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForUnsign(senderAddress, { + const transaction = await factory.createTransactionForUnsign(senderAddress, { multisigContract: multisigContractAddress, actionId: 42, gasLimit: 5000000n, @@ -515,12 +543,12 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "unsign@2a"); }); - it("should create transaction for unsign for outdated board members", function () { + it("should create transaction for unsign for outdated board members", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForUnsignForOutdatedBoardMembers(senderAddress, { + const transaction = await factory.createTransactionForUnsignForOutdatedBoardMembers(senderAddress, { multisigContract: multisigContractAddress, actionId: 42, outdatedBoardMembers: [1, 3, 5], @@ -533,12 +561,12 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "unsignForOutdatedBoardMembers@2a@01@03@05"); }); - it("should create transaction for perform action", function () { + it("should create transaction for perform action", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForPerformAction(senderAddress, { + const transaction = await factory.createTransactionForPerformAction(senderAddress, { multisigContract: multisigContractAddress, actionId: 42, gasLimit: 5000000n, @@ -550,12 +578,12 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "performAction@2a"); }); - it("should create transaction for perform batch", function () { + it("should create transaction for perform batch", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForPerformBatch(senderAddress, { + const transaction = await factory.createTransactionForPerformBatch(senderAddress, { multisigContract: multisigContractAddress, groupId: 5, gasLimit: 5000000n, @@ -567,13 +595,13 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "performBatch@05"); }); - it("should create transaction for discard action", function () { + it("should create transaction for discard action", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForDiscardAction(senderAddress, { + const transaction = await factory.createTransactionForDiscardAction(senderAddress, { multisigContract: multisigContractAddress, actionId: 322, gasLimit: 5000000n, @@ -586,13 +614,13 @@ describe("test multisig transactions factory", function () { assert.deepEqual(transaction.data.toString(), "discardAction@0142"); }); - it("should create transaction for discard batch", function () { + it("should create transaction for discard batch", async function () { const senderAddress = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); const multisigContractAddress = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", ); - const transaction = factory.createTransactionForDiscardBatch(senderAddress, { + const transaction = await factory.createTransactionForDiscardBatch(senderAddress, { multisigContract: multisigContractAddress, gasLimit: 5000000n, actionIds: [24, 25], diff --git a/src/multisig/multisigTransactionsFactory.ts b/src/multisig/multisigTransactionsFactory.ts index cf25aee22..5e73e15b2 100644 --- a/src/multisig/multisigTransactionsFactory.ts +++ b/src/multisig/multisigTransactionsFactory.ts @@ -16,10 +16,10 @@ import { U64Value, VariadicValue, } from "../abi"; -import { Err, TokenComputer, TransactionsFactoryConfig } from "../core"; +import { Err, IGasLimitEstimator, TokenComputer, TransactionsFactoryConfig } from "../core"; import { Address } from "../core/address"; +import { BaseFactory } from "../core/baseFactory"; import { Transaction } from "../core/transaction"; -import { TransactionBuilder } from "../core/transactionBuilder"; import { SmartContractTransactionsFactory } from "../smartContracts"; import { ProposeTransferExecuteContractInput } from "./proposeTransferExecuteContractInput"; import * as resources from "./resources"; @@ -35,13 +35,14 @@ interface IConfig { * Use this class to create multisig related transactions like creating a new multisig contract, * proposing actions, signing actions, and performing actions. */ -export class MultisigTransactionsFactory { +export class MultisigTransactionsFactory extends BaseFactory { private readonly argSerializer: ArgSerializer; private readonly smartContractFactory: SmartContractTransactionsFactory; private readonly config: IConfig; private readonly abi: Abi; - constructor(options: { config: TransactionsFactoryConfig; abi: Abi }) { + constructor(options: { config: TransactionsFactoryConfig; abi: Abi; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); this.config = options.config; this.abi = options.abi; this.argSerializer = new ArgSerializer(); @@ -51,11 +52,14 @@ export class MultisigTransactionsFactory { /** * Creates a transaction to deploy a new multisig contract */ - createTransactionForDeploy(sender: Address, options: resources.DeployMultisigContractInput): Transaction { + async createTransactionForDeploy( + sender: Address, + options: resources.DeployMultisigContractInput, + ): Promise { const boardAddresses: AddressValue[] = options.board.map((addr) => new AddressValue(addr)); const args = [new U32Value(options.quorum), VariadicValue.fromItems(...boardAddresses)]; - return this.smartContractFactory.createTransactionForDeploy(sender, { + return await this.smartContractFactory.createTransactionForDeploy(sender, { bytecode: options.bytecode, gasLimit: options.gasLimit, isUpgradeable: options.isUpgradeable, @@ -69,11 +73,11 @@ export class MultisigTransactionsFactory { /** * Proposes adding a new board member */ - createTransactionForProposeAddBoardMember( + async createTransactionForProposeAddBoardMember( sender: Address, options: resources.ProposeAddBoardMemberInput, - ): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + ): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeAddBoardMember", gasLimit: options.gasLimit, @@ -84,8 +88,11 @@ export class MultisigTransactionsFactory { /** * Proposes adding a new proposer */ - createTransactionForProposeAddProposer(sender: Address, options: resources.ProposeAddProposerInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForProposeAddProposer( + sender: Address, + options: resources.ProposeAddProposerInput, + ): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeAddProposer", gasLimit: options.gasLimit, @@ -96,8 +103,11 @@ export class MultisigTransactionsFactory { /** * Proposes removing a user (board member or proposer) */ - createTransactionForProposeRemoveUser(sender: Address, options: resources.ProposeRemoveUserInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForProposeRemoveUser( + sender: Address, + options: resources.ProposeRemoveUserInput, + ): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeRemoveUser", gasLimit: options.gasLimit, @@ -108,8 +118,11 @@ export class MultisigTransactionsFactory { /** * Proposes changing the quorum (minimum signatures required) */ - createTransactionForProposeChangeQuorum(sender: Address, options: resources.ProposeChangeQuorumInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForProposeChangeQuorum( + sender: Address, + options: resources.ProposeChangeQuorumInput, + ): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeChangeQuorum", gasLimit: options.gasLimit, @@ -120,14 +133,14 @@ export class MultisigTransactionsFactory { /** * Proposes a transaction that will transfer EGLD and/or execute a function */ - createTransactionForProposeTransferExecute( + async createTransactionForProposeTransferExecute( sender: Address, options: resources.ProposeTransferExecuteInput, - ): Transaction { + ): Promise { const gasOption = options.optGasLimit ? new U64Value(options.optGasLimit) : null; let functionCall = []; if (options.functionName) { - const input = ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ + const input = await ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ multisig: options.multisigContract, to: options.to, functionName: options.functionName, @@ -137,7 +150,7 @@ export class MultisigTransactionsFactory { functionCall = input.functionCall; } - return this.smartContractFactory.createTransactionForExecute(sender, { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeTransferExecute", gasLimit: options.gasLimit, @@ -153,8 +166,8 @@ export class MultisigTransactionsFactory { /** * Proposes a transaction that will transfer EGLD and/or execute a function */ - createTransactionForDeposit(sender: Address, options: resources.DepositExecuteInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForDeposit(sender: Address, options: resources.DepositExecuteInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "deposit", gasLimit: options.gasLimit, @@ -167,11 +180,11 @@ export class MultisigTransactionsFactory { /** * Proposes a transaction that will transfer ESDT tokens and/or execute a function */ - createTransactionForProposeTransferExecuteEsdt( + async createTransactionForProposeTransferExecuteEsdt( sender: Address, options: resources.ProposeTransferExecuteEsdtInput, - ): Transaction { - const input = ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ + ): Promise { + const input = await ProposeTransferExecuteContractInput.newFromTransferExecuteInput({ multisig: options.multisigContract, to: options.to, functionName: options.functionName, @@ -189,14 +202,18 @@ export class MultisigTransactionsFactory { ), ), ]; - return new TransactionBuilder({ - config: this.config, + + const transaction = new Transaction({ sender: sender, receiver: options.multisigContract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, options.gasLimit); + + return transaction; } private mapTokenPayments(options: resources.ProposeTransferExecuteEsdtInput): resources.EsdtTokenPayment[] { @@ -215,8 +232,11 @@ export class MultisigTransactionsFactory { /** * Proposes an async call to another contract */ - createTransactionForProposeAsyncCall(sender: Address, options: resources.ProposeAsyncCallInput): Transaction { - const input = ProposeTransferExecuteContractInput.newFromProposeAsyncCallInput({ + async createTransactionForProposeAsyncCall( + sender: Address, + options: resources.ProposeAsyncCallInput, + ): Promise { + const input = await ProposeTransferExecuteContractInput.newFromProposeAsyncCallInput({ multisig: options.multisigContract, to: options.to, tokenTransfers: options.tokenTransfers, @@ -225,7 +245,7 @@ export class MultisigTransactionsFactory { abi: options.abi, }); - return this.smartContractFactory.createTransactionForExecute(sender, { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeAsyncCall", gasLimit: options.gasLimit, @@ -241,12 +261,12 @@ export class MultisigTransactionsFactory { /** * Proposes deploying a smart contract from source */ - createTransactionForProposeContractDeployFromSource( + async createTransactionForProposeContractDeployFromSource( sender: Address, options: resources.ProposeContractDeployFromSourceInput, - ): Transaction { + ): Promise { let args: TypedValue[] = this.argsToTypedValues(options.arguments, options.abi?.constructorDefinition); - return this.smartContractFactory.createTransactionForExecute(sender, { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeSCDeployFromSource", gasLimit: options.gasLimit, @@ -262,12 +282,12 @@ export class MultisigTransactionsFactory { /** * Proposes upgrading a smart contract from source */ - createTransactionForProposeContractUpgradeFromSource( + async createTransactionForProposeContractUpgradeFromSource( sender: Address, options: resources.ProposeContractUpgradeFromSourceInput, - ): Transaction { + ): Promise { let args: TypedValue[] = this.argsToTypedValues(options.arguments, options.abi?.constructorDefinition); - return this.smartContractFactory.createTransactionForExecute(sender, { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "proposeSCUpgradeFromSource", gasLimit: options.gasLimit, @@ -284,8 +304,8 @@ export class MultisigTransactionsFactory { /** * Signs an action (by a board member) */ - createTransactionForSignAction(sender: Address, options: resources.ActionInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForSignAction(sender: Address, options: resources.ActionInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "sign", gasLimit: options.gasLimit, @@ -296,8 +316,8 @@ export class MultisigTransactionsFactory { /** * Signs all actions in a batch */ - createTransactionForSignBatch(sender: Address, options: resources.GroupInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForSignBatch(sender: Address, options: resources.GroupInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "signBatch", gasLimit: options.gasLimit, @@ -308,8 +328,8 @@ export class MultisigTransactionsFactory { /** * Signs and performs an action in one transaction */ - createTransactionForSignAndPerform(sender: Address, options: resources.ActionInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForSignAndPerform(sender: Address, options: resources.ActionInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "signAndPerform", gasLimit: options.gasLimit, @@ -320,8 +340,11 @@ export class MultisigTransactionsFactory { /** * Signs and performs all actions in a batch */ - createTransactionForSignBatchAndPerform(sender: Address, options: resources.GroupInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForSignBatchAndPerform( + sender: Address, + options: resources.GroupInput, + ): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "signBatchAndPerform", gasLimit: options.gasLimit, @@ -332,8 +355,8 @@ export class MultisigTransactionsFactory { /** * Withdraws signature from an action */ - createTransactionForUnsign(sender: Address, options: resources.ActionInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForUnsign(sender: Address, options: resources.ActionInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "unsign", gasLimit: options.gasLimit, @@ -344,8 +367,8 @@ export class MultisigTransactionsFactory { /** * Withdraws signatures from all actions in a batch */ - createTransactionForUnsignBatch(sender: Address, options: resources.GroupInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForUnsignBatch(sender: Address, options: resources.GroupInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "unsignBatch", gasLimit: options.gasLimit, @@ -356,12 +379,12 @@ export class MultisigTransactionsFactory { /** * Removes signatures from outdated board members */ - createTransactionForUnsignForOutdatedBoardMembers( + async createTransactionForUnsignForOutdatedBoardMembers( sender: Address, options: resources.UnsignForOutdatedBoardMembersInput, - ): Transaction { + ): Promise { const outdatedBoardMembers: U32Value[] = options.outdatedBoardMembers.map((id) => new U32Value(id)); - return this.smartContractFactory.createTransactionForExecute(sender, { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "unsignForOutdatedBoardMembers", gasLimit: options.gasLimit, @@ -372,8 +395,8 @@ export class MultisigTransactionsFactory { /** * Performs an action that has reached quorum */ - createTransactionForPerformAction(sender: Address, options: resources.ActionInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForPerformAction(sender: Address, options: resources.ActionInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "performAction", gasLimit: options.gasLimit, @@ -384,8 +407,8 @@ export class MultisigTransactionsFactory { /** * Performs all actions in a batch that have reached quorum */ - createTransactionForPerformBatch(sender: Address, options: resources.GroupInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForPerformBatch(sender: Address, options: resources.GroupInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "performBatch", gasLimit: options.gasLimit, @@ -396,8 +419,8 @@ export class MultisigTransactionsFactory { /** * Discards an action that is no longer needed */ - createTransactionForDiscardAction(sender: Address, options: resources.ActionInput): Transaction { - return this.smartContractFactory.createTransactionForExecute(sender, { + async createTransactionForDiscardAction(sender: Address, options: resources.ActionInput): Promise { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "discardAction", gasLimit: options.gasLimit, @@ -408,9 +431,12 @@ export class MultisigTransactionsFactory { /** * Discards all actions in the provided list */ - createTransactionForDiscardBatch(sender: Address, options: resources.DiscardBatchInput): Transaction { + async createTransactionForDiscardBatch( + sender: Address, + options: resources.DiscardBatchInput, + ): Promise { const actionIdsArgs = options.actionIds.map((id) => new U32Value(id)); - return this.smartContractFactory.createTransactionForExecute(sender, { + return await this.smartContractFactory.createTransactionForExecute(sender, { contract: options.multisigContract, function: "discardBatch", gasLimit: options.gasLimit, diff --git a/src/multisig/proposeTransferExecuteContractInput.ts b/src/multisig/proposeTransferExecuteContractInput.ts index 3ae252f35..5a49dd33b 100644 --- a/src/multisig/proposeTransferExecuteContractInput.ts +++ b/src/multisig/proposeTransferExecuteContractInput.ts @@ -17,19 +17,19 @@ export class ProposeTransferExecuteContractInput { this.functionCall = options.functionCall; } - static newFromTransferExecuteInput(options: { + static async newFromTransferExecuteInput(options: { multisig: Address; to: Address; functionName: string; arguments?: any[]; optGasLimit?: bigint; abi?: Abi; - }): ProposeTransferExecuteContractInput { + }): Promise { const transactionsFactory = new SmartContractTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: "" }), abi: options.abi, }); - const transaction = transactionsFactory.createTransactionForExecute(Address.empty(), { + const transaction = await transactionsFactory.createTransactionForExecute(Address.empty(), { contract: Address.empty(), function: options.functionName, gasLimit: 0n, @@ -47,7 +47,7 @@ export class ProposeTransferExecuteContractInput { }); } - static newFromProposeAsyncCallInput(options: { + static async newFromProposeAsyncCallInput(options: { multisig: Address; to: Address; tokenTransfers: TokenTransfer[]; @@ -55,12 +55,12 @@ export class ProposeTransferExecuteContractInput { arguments: any[]; optGasLimit?: bigint; abi?: Abi; - }): ProposeTransferExecuteContractInput { + }): Promise { const transactionsFactory = new SmartContractTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: "" }), abi: options.abi, }); - const transaction = transactionsFactory.createTransactionForExecute(Address.empty(), { + const transaction = await transactionsFactory.createTransactionForExecute(Address.empty(), { contract: Address.empty(), function: options.functionName, gasLimit: 0n, diff --git a/src/multisig/resources.ts b/src/multisig/resources.ts index edc9ee73d..07e532778 100644 --- a/src/multisig/resources.ts +++ b/src/multisig/resources.ts @@ -11,12 +11,12 @@ export type DeployMultisigContractInput = { isReadable?: boolean; isPayable?: boolean; isPayableBySmartContract?: boolean; - gasLimit: bigint; + gasLimit?: bigint; }; export type MultisigContractInput = { multisigContract: Address; - gasLimit: bigint; + gasLimit?: bigint; }; export type ProposeAddBoardMemberInput = MultisigContractInput & { diff --git a/src/networkProviders/apiNetworkProvider.dev.net.spec.ts b/src/networkProviders/apiNetworkProvider.dev.net.spec.ts index 14de6dac9..74d7616a2 100644 --- a/src/networkProviders/apiNetworkProvider.dev.net.spec.ts +++ b/src/networkProviders/apiNetworkProvider.dev.net.spec.ts @@ -403,4 +403,19 @@ describe("ApiNetworkProvider Tests", function () { ); assert.isTrue(transactions.length > 0); }); + + it("should estimate transaction cost", async function () { + const bob = await Account.newFromPem(`${getTestWalletsPath()}/bob.pem`); + let transaction = new Transaction({ + sender: bob.address, + receiver: bob.address, + gasLimit: 0n, + chainID: "D", + }); + + const gasCost = await apiProvider.estimateTransactionCost(transaction); + assert.equal(gasCost.gasLimit, 50000); + assert.equal(transaction.nonce, 0n); + assert.deepEqual(transaction.signature, new Uint8Array()); + }); }); diff --git a/src/networkProviders/apiNetworkProvider.ts b/src/networkProviders/apiNetworkProvider.ts index 14fed01c0..b23b3b939 100644 --- a/src/networkProviders/apiNetworkProvider.ts +++ b/src/networkProviders/apiNetworkProvider.ts @@ -125,9 +125,7 @@ export class ApiNetworkProvider implements INetworkProvider { } async estimateTransactionCost(tx: Transaction): Promise { - const transaction = prepareTransactionForBroadcasting(tx); - const response = await this.doPostGeneric("transaction/cost", transaction); - return TransactionCostResponse.fromHttpResponse(response.data); + return this.backingProxyNetworkProvider.estimateTransactionCost(tx); } async sendTransactions(txs: Transaction[]): Promise<[number, string[]]> { diff --git a/src/networkProviders/proxyNetworkProvider.ts b/src/networkProviders/proxyNetworkProvider.ts index f62f632bb..09cdc97b2 100644 --- a/src/networkProviders/proxyNetworkProvider.ts +++ b/src/networkProviders/proxyNetworkProvider.ts @@ -129,7 +129,24 @@ export class ProxyNetworkProvider implements INetworkProvider { } async estimateTransactionCost(tx: Transaction): Promise { - const transaction = prepareTransactionForBroadcasting(tx); + const copiedTx = Transaction.newFromPlainObject(tx.toPlainObject()); + if (!copiedTx.nonce) { + copiedTx.nonce = (await this.getAccount(copiedTx.sender)).nonce; + } + + if (!copiedTx.signature.length) { + copiedTx.signature = new Uint8Array(64); + } + + if (!copiedTx.guardian.isEmpty() && !copiedTx.guardianSignature.length) { + copiedTx.guardianSignature = new Uint8Array(64); + } + + if (!copiedTx.relayer.isEmpty() && !copiedTx.relayerSignature.length) { + copiedTx.relayerSignature = new Uint8Array(64); + } + + const transaction = prepareTransactionForBroadcasting(copiedTx); const response = await this.doPostGeneric("transaction/cost", transaction); return TransactionCostResponse.fromHttpResponse(response); } diff --git a/src/smartContracts/resources.ts b/src/smartContracts/resources.ts index 508aab388..9d59f9324 100644 --- a/src/smartContracts/resources.ts +++ b/src/smartContracts/resources.ts @@ -3,7 +3,11 @@ import { TokenTransfer } from "../core/tokens"; export type ContractDeployInput = { bytecode: Uint8Array; - gasLimit: bigint; + /** + * The gas limit for the operation. If not provided, a gas limit estimator must be used. + * Failure to provide this parameter without an estimator may lead to runtime errors. + */ + gasLimit?: bigint; arguments?: any[]; nativeTransferAmount?: bigint; isUpgradeable?: boolean; @@ -14,7 +18,7 @@ export type ContractDeployInput = { export type ContractExecuteInput = { contract: Address; - gasLimit: bigint; + gasLimit?: bigint; function: string; arguments?: any[]; nativeTransferAmount?: bigint; diff --git a/src/smartContracts/smartContractController.spec.ts b/src/smartContracts/smartContractController.spec.ts index a6b36a0e7..2dc58fe12 100644 --- a/src/smartContracts/smartContractController.spec.ts +++ b/src/smartContracts/smartContractController.spec.ts @@ -1,8 +1,10 @@ import BigNumber from "bignumber.js"; import { assert } from "chai"; import { Abi, BigUIntValue, BooleanValue, BytesValue, Tuple, U16Value, U64Value } from "../abi"; +import { Account } from "../accounts"; import { Address, SmartContractQueryResponse } from "../core"; -import { MockNetworkProvider, loadAbiRegistry } from "../testutils"; +import { GasLimitEstimator } from "../gasEstimator"; +import { MockNetworkProvider, getTestWalletsPath, loadAbiRegistry } from "../testutils"; import { bigIntToBuffer } from "../tokenOperations/codec"; import { SmartContractController } from "./smartContractController"; @@ -234,4 +236,45 @@ describe("test smart contract queries controller", () => { }); }); }); + + describe("set gasLimit", () => { + it("should set the specified gasLimit", async function () { + const alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); + const networkProvider = new MockNetworkProvider(); + const controller = new SmartContractController({ + chainID: "D", + networkProvider: networkProvider, + }); + + const transaction = await controller.createTransactionForExecute(alice, 0n, { + contract: Address.empty(), + function: "dummyFunction", + arguments: [], + gasLimit: 123456789n, + }); + + assert.equal(transaction.gasLimit, 123456789n); + }); + + it("should set the specified gasLimit even when gasLimitEstimator is provided", async function () { + const alice = await Account.newFromPem(`${getTestWalletsPath()}/alice.pem`); + const networkProvider = new MockNetworkProvider(); + + const gasLimitEstimator = new GasLimitEstimator({ networkProvider: networkProvider, gasMultiplier: 1.5 }); + const controller = new SmartContractController({ + chainID: "D", + networkProvider: networkProvider, + gasLimitEstimator: gasLimitEstimator, + }); + + const transaction = await controller.createTransactionForExecute(alice, 0n, { + contract: Address.empty(), + function: "dummyFunction", + arguments: [], + gasLimit: 123456789n, + }); + + assert.equal(transaction.gasLimit, 123456789n); + }); + }); }); diff --git a/src/smartContracts/smartContractController.ts b/src/smartContracts/smartContractController.ts index d1e610eb7..34d545246 100644 --- a/src/smartContracts/smartContractController.ts +++ b/src/smartContracts/smartContractController.ts @@ -6,6 +6,7 @@ import { Err, ErrSmartContractQuery, IAccount, + IGasLimitEstimator, SmartContractQuery, SmartContractQueryInput, SmartContractQueryResponse, @@ -26,11 +27,17 @@ export class SmartContractController extends BaseController { private networkProvider: INetworkProvider; protected abi?: Abi; - constructor(options: { chainID: string; networkProvider: INetworkProvider; abi?: Abi }) { + constructor(options: { + chainID: string; + networkProvider: INetworkProvider; + abi?: Abi; + gasLimitEstimator?: IGasLimitEstimator; + }) { super(); this.factory = new SmartContractTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), abi: options.abi, + gasLimitEstimator: options.gasLimitEstimator, }); this.parser = new SmartContractTransactionsOutcomeParser(options); this.transactionWatcher = new TransactionWatcher(options.networkProvider); @@ -43,7 +50,7 @@ export class SmartContractController extends BaseController { nonce: bigint, options: resources.ContractDeployInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForDeploy(sender.address, options); + const transaction = await this.factory.createTransactionForDeploy(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -69,7 +76,7 @@ export class SmartContractController extends BaseController { nonce: bigint, options: resources.ContractUpgradeInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUpgrade(sender.address, options); + const transaction = await this.factory.createTransactionForUpgrade(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -86,7 +93,7 @@ export class SmartContractController extends BaseController { nonce: bigint, options: resources.ContractExecuteInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForExecute(sender.address, options); + const transaction = await this.factory.createTransactionForExecute(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); diff --git a/src/smartContracts/smartContractTransactionsFactory.spec.ts b/src/smartContracts/smartContractTransactionsFactory.spec.ts index 15363f558..2623fd151 100644 --- a/src/smartContracts/smartContractTransactionsFactory.spec.ts +++ b/src/smartContracts/smartContractTransactionsFactory.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Abi, Code, U32Value } from "../abi"; +import { Abi, U32Value } from "../abi"; import { Address, Err, Token, TokenTransfer, TransactionsFactoryConfig } from "../core"; import { loadAbiRegistry, loadContractCode } from "../testutils/utils"; import { SmartContractTransactionsFactory } from "./smartContractTransactionsFactory"; @@ -8,7 +8,7 @@ describe("test smart contract transactions factory", function () { const config = new TransactionsFactoryConfig({ chainID: "D" }); let factory: SmartContractTransactionsFactory; let abiAwareFactory: SmartContractTransactionsFactory; - let bytecode: Code; + let bytecode: Uint8Array; let abi: Abi; before(async function () { @@ -30,16 +30,18 @@ describe("test smart contract transactions factory", function () { const gasLimit = 6000000n; const args = [0]; - assert.throws( - () => - factory.createTransactionForDeploy(sender, { - bytecode: bytecode.valueOf(), - gasLimit: gasLimit, - arguments: args, - }), - Err, - "Can't convert args to TypedValues", - ); + try { + await factory.createTransactionForDeploy(sender, { + bytecode: bytecode.valueOf(), + gasLimit: gasLimit, + arguments: args, + }); + + assert.fail("Expected error was not thrown"); + } catch (err) { + assert.instanceOf(err, Err); + assert.match(err.message, /Can't convert args to TypedValues/); + } }); it("should create 'Transaction' for deploy", async function () { @@ -47,17 +49,18 @@ describe("test smart contract transactions factory", function () { const gasLimit = 6000000n; const args = [new U32Value(1)]; - const transaction = factory.createTransactionForDeploy(sender, { + const transaction = await factory.createTransactionForDeploy(sender, { bytecode: bytecode.valueOf(), gasLimit: gasLimit, arguments: args, }); - const transactionAbiAware = abiAwareFactory.createTransactionForDeploy(sender, { - bytecode: bytecode.valueOf(), + const transactionAbiAware = await abiAwareFactory.createTransactionForDeploy(sender, { + bytecode: bytecode, gasLimit: gasLimit, arguments: args, }); + const bytecodeHex = Buffer.from(bytecode).toString("hex"); assert.deepEqual( transaction.sender, @@ -67,7 +70,7 @@ describe("test smart contract transactions factory", function () { transaction.receiver, Address.newFromBech32("erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"), ); - assert.deepEqual(transaction.data, Buffer.from(`${bytecode}@0500@0504@01`)); + assert.deepEqual(transaction.data, Buffer.from(`${bytecodeHex}@0500@0504@01`)); assert.equal(transaction.gasLimit.valueOf(), gasLimit); assert.equal(transaction.value, 0n); @@ -81,14 +84,14 @@ describe("test smart contract transactions factory", function () { const gasLimit = 6000000n; const args = [new U32Value(7)]; - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, arguments: args, }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -117,7 +120,7 @@ describe("test smart contract transactions factory", function () { const gasLimit = 6000000n; const egldAmount = 1000000000000000000n; - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -125,7 +128,7 @@ describe("test smart contract transactions factory", function () { nativeTransferAmount: egldAmount, }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -157,7 +160,7 @@ describe("test smart contract transactions factory", function () { const token = new Token({ identifier: "FOO-6ce17b", nonce: 0n }); const transfer = new TokenTransfer({ token, amount: 10n }); - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -165,7 +168,7 @@ describe("test smart contract transactions factory", function () { tokenTransfers: [transfer], }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -197,7 +200,7 @@ describe("test smart contract transactions factory", function () { const token = new Token({ identifier: "EGLD-000000", nonce: 0n }); const transfer = new TokenTransfer({ token, amount: 10n }); - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -205,7 +208,7 @@ describe("test smart contract transactions factory", function () { tokenTransfers: [transfer], }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -245,7 +248,7 @@ describe("test smart contract transactions factory", function () { const barToken = new Token({ identifier: "BAR-5bc08f", nonce: 0n }); const barTransfer = new TokenTransfer({ token: barToken, amount: 3140n }); - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -253,7 +256,7 @@ describe("test smart contract transactions factory", function () { tokenTransfers: [fooTransfer, barTransfer], }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -293,7 +296,7 @@ describe("test smart contract transactions factory", function () { const token = new Token({ identifier: "NFT-123456", nonce: 1n }); const transfer = new TokenTransfer({ token, amount: 1n }); - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -301,7 +304,7 @@ describe("test smart contract transactions factory", function () { tokenTransfers: [transfer], }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -344,7 +347,7 @@ describe("test smart contract transactions factory", function () { const secondToken = new Token({ identifier: "NFT-123456", nonce: 42n }); const secondTransfer = new TokenTransfer({ token: secondToken, amount: 1n }); - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -352,7 +355,7 @@ describe("test smart contract transactions factory", function () { tokenTransfers: [firstTransfer, secondTransfer], }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -395,7 +398,7 @@ describe("test smart contract transactions factory", function () { const secondToken = new Token({ identifier: "NFT-123456", nonce: 42n }); const secondTransfer = new TokenTransfer({ token: secondToken, amount: 1n }); - const transaction = factory.createTransactionForExecute(sender, { + const transaction = await factory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -404,7 +407,7 @@ describe("test smart contract transactions factory", function () { tokenTransfers: [firstTransfer, secondTransfer], }); - const transactionAbiAware = abiAwareFactory.createTransactionForExecute(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForExecute(sender, { contract: contract, function: func, gasLimit: gasLimit, @@ -442,19 +445,20 @@ describe("test smart contract transactions factory", function () { const gasLimit = 6000000n; const args = [new U32Value(7)]; - const transaction = factory.createTransactionForUpgrade(sender, { + const transaction = await factory.createTransactionForUpgrade(sender, { contract: contract, - bytecode: bytecode.valueOf(), + bytecode: bytecode, gasLimit: gasLimit, arguments: args, }); - const transactionAbiAware = abiAwareFactory.createTransactionForUpgrade(sender, { + const transactionAbiAware = await abiAwareFactory.createTransactionForUpgrade(sender, { contract: contract, - bytecode: bytecode.valueOf(), + bytecode: bytecode, gasLimit: gasLimit, arguments: args, }); + const bytecodeHex = Buffer.from(bytecode).toString("hex"); assert.deepEqual( transaction.sender, @@ -464,7 +468,7 @@ describe("test smart contract transactions factory", function () { transaction.receiver, Address.newFromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"), ); - assert.deepEqual(transaction.data!, Buffer.from(`upgradeContract@${bytecode}@0504@07`)); + assert.deepEqual(transaction.data!, Buffer.from(`upgradeContract@${bytecodeHex}@0504@07`)); assert.equal(transaction.gasLimit, gasLimit); assert.equal(transaction.value, 0n); @@ -519,7 +523,7 @@ describe("test smart contract transactions factory", function () { const gasLimit = 6000000n; // By default, use the upgrade constructor. - const tx1 = factory.createTransactionForUpgrade(sender, { + const tx1 = await factory.createTransactionForUpgrade(sender, { contract: receiver, bytecode: bytecode, gasLimit: gasLimit, @@ -531,7 +535,7 @@ describe("test smart contract transactions factory", function () { // Fallback to the "upgrade" endpoint. (abi).upgradeConstructorDefinition = undefined; - const tx2 = factory.createTransactionForUpgrade(sender, { + const tx2 = await factory.createTransactionForUpgrade(sender, { contract: receiver, bytecode: bytecode, gasLimit: gasLimit, @@ -543,7 +547,7 @@ describe("test smart contract transactions factory", function () { // Fallback to the constructor. (abi).endpoints.length = 0; - const tx3 = factory.createTransactionForUpgrade(sender, { + const tx3 = await factory.createTransactionForUpgrade(sender, { contract: receiver, bytecode: bytecode, gasLimit: gasLimit, @@ -555,23 +559,26 @@ describe("test smart contract transactions factory", function () { // No fallbacks. (abi).constructorDefinition = undefined; - assert.throws( - () => - factory.createTransactionForUpgrade(sender, { - contract: receiver, - bytecode: bytecode, - gasLimit: gasLimit, - arguments: [42], - }), - "Can't convert args to TypedValues", - ); + try { + await factory.createTransactionForUpgrade(sender, { + contract: receiver, + bytecode: bytecode, + gasLimit: gasLimit, + arguments: [42], + }); + + assert.fail("Expected error was not thrown"); + } catch (err) { + assert.instanceOf(err, Err); + assert.match(err.message, /Can't convert args to TypedValues/); + } }); it("should create 'Transaction' for claiming developer rewards", async function () { const sender = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); - const transaction = factory.createTransactionForClaimingDeveloperRewards({ + const transaction = await factory.createTransactionForClaimingDeveloperRewards({ sender: sender, contract: contract, }); @@ -585,7 +592,7 @@ describe("test smart contract transactions factory", function () { Address.newFromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"), ); assert.equal(Buffer.from(transaction.data).toString(), "ClaimDeveloperRewards"); - assert.equal(transaction.gasLimit, 6000000n); + assert.equal(transaction.gasLimit, 6081500n); assert.equal(transaction.value, 0n); }); @@ -594,7 +601,7 @@ describe("test smart contract transactions factory", function () { const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const newOwner = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - const transaction = factory.createTransactionForChangingOwnerAddress({ + const transaction = await factory.createTransactionForChangingOwnerAddress({ sender: sender, contract: contract, newOwner: newOwner, @@ -612,7 +619,7 @@ describe("test smart contract transactions factory", function () { Buffer.from(transaction.data).toString(), "ChangeOwnerAddress@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", ); - assert.equal(transaction.gasLimit, 6000000n); + assert.equal(transaction.gasLimit, 6174500n); assert.equal(transaction.value, 0n); }); }); diff --git a/src/smartContracts/smartContractTransactionsFactory.ts b/src/smartContracts/smartContractTransactionsFactory.ts index 83136bd2f..c95939f28 100644 --- a/src/smartContracts/smartContractTransactionsFactory.ts +++ b/src/smartContracts/smartContractTransactionsFactory.ts @@ -1,5 +1,6 @@ import { Abi, ArgSerializer, EndpointDefinition, isTyped, NativeSerializer } from "../abi"; -import { Address, CodeMetadata } from "../core"; +import { Address, CodeMetadata, IGasLimitEstimator } from "../core"; +import { BaseFactory } from "../core/baseFactory"; import { CONTRACT_DEPLOY_ADDRESS_HEX, EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER, @@ -10,7 +11,6 @@ import { Logger } from "../core/logger"; import { TokenComputer, TokenTransfer } from "../core/tokens"; import { TokenTransfersDataBuilder } from "../core/tokenTransfersDataBuilder"; import { Transaction } from "../core/transaction"; -import { TransactionBuilder } from "../core/transactionBuilder"; import { byteArrayToHex, utf8ToHex } from "../core/utils.codec"; import * as resources from "./resources"; @@ -26,14 +26,15 @@ interface IConfig { /** * Use this class to create transactions to deploy, call or upgrade a smart contract. */ -export class SmartContractTransactionsFactory { +export class SmartContractTransactionsFactory extends BaseFactory { private readonly config: IConfig; private readonly abi?: Abi; private readonly tokenComputer: TokenComputer; private readonly dataArgsBuilder: TokenTransfersDataBuilder; private readonly contractDeployAddress: Address; - constructor(options: { config: IConfig; abi?: Abi }) { + constructor(options: { config: IConfig; abi?: Abi; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); this.config = options.config; this.abi = options.abi; this.tokenComputer = new TokenComputer(); @@ -41,7 +42,7 @@ export class SmartContractTransactionsFactory { this.contractDeployAddress = Address.newFromHex(CONTRACT_DEPLOY_ADDRESS_HEX, this.config.addressHrp); } - createTransactionForDeploy(sender: Address, options: resources.ContractDeployInput): Transaction { + async createTransactionForDeploy(sender: Address, options: resources.ContractDeployInput): Promise { const nativeTransferAmount = options.nativeTransferAmount ?? 0n; const isUpgradeable = options.isUpgradeable ?? true; const isReadable = options.isReadable ?? true; @@ -55,18 +56,21 @@ export class SmartContractTransactionsFactory { const preparedArgs = this.argsToDataParts(args, endpoint); dataParts.push(...preparedArgs); - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: this.contractDeployAddress, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - amount: nativeTransferAmount, - }).build(); + value: nativeTransferAmount, + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, options.gasLimit); + + return transaction; } - createTransactionForExecute(sender: Address, options: resources.ContractExecuteInput): Transaction { + async createTransactionForExecute(sender: Address, options: resources.ContractExecuteInput): Promise { const args = options.arguments || []; let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; let nativeTransferAmount = options.nativeTransferAmount ?? 0n; @@ -105,18 +109,21 @@ export class SmartContractTransactionsFactory { const preparedArgs = this.argsToDataParts(args, endpoint); dataParts.push(...preparedArgs); - return new TransactionBuilder({ - config: this.config, - sender: sender, - receiver: receiver, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - amount: nativeTransferAmount, - }).build(); + const transaction = new Transaction({ + sender, + receiver, + value: nativeTransferAmount, + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, options.gasLimit); + + return transaction; } - createTransactionForUpgrade(sender: Address, options: resources.ContractUpgradeInput): Transaction { + async createTransactionForUpgrade(sender: Address, options: resources.ContractUpgradeInput): Promise { const nativeTransferAmount = options.nativeTransferAmount ?? 0n; const isUpgradeable = options.isUpgradeable ?? true; @@ -132,15 +139,18 @@ export class SmartContractTransactionsFactory { const preparedArgs = this.argsToDataParts(args, endpoint); dataParts.push(...preparedArgs); - return new TransactionBuilder({ - config: this.config, - sender: sender, + const transaction = new Transaction({ + sender, receiver: options.contract, - dataParts: dataParts, - gasLimit: options.gasLimit, - addDataMovementGas: false, - amount: nativeTransferAmount, - }).build(); + value: nativeTransferAmount, + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, options.gasLimit); + + return transaction; } private getEndpointForUpgrade(): EndpointDefinition | undefined { @@ -165,34 +175,45 @@ export class SmartContractTransactionsFactory { } } - createTransactionForClaimingDeveloperRewards(options: { sender: Address; contract: Address }): Transaction { + async createTransactionForClaimingDeveloperRewards(options: { + sender: Address; + contract: Address; + gasLimit?: bigint; + }): Promise { const dataParts = ["ClaimDeveloperRewards"]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: options.sender, receiver: options.contract, - dataParts: dataParts, - gasLimit: this.config.gasLimitClaimDeveloperRewards, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, options.gasLimit, this.config.gasLimitClaimDeveloperRewards); + + return transaction; } - createTransactionForChangingOwnerAddress(options: { + async createTransactionForChangingOwnerAddress(options: { sender: Address; contract: Address; newOwner: Address; - }): Transaction { + gasLimit?: bigint; + }): Promise { const dataParts = ["ChangeOwnerAddress", options.newOwner.toHex()]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: options.sender, receiver: options.contract, - dataParts: dataParts, - gasLimit: this.config.gasLimitChangeOwnerAddress, - addDataMovementGas: false, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, options.gasLimit, this.config.gasLimitChangeOwnerAddress); + + return transaction; } protected argsToDataParts(args: any[], endpoint?: EndpointDefinition): string[] { diff --git a/src/testutils/mockNetworkProvider.ts b/src/testutils/mockNetworkProvider.ts index 5760c970e..11ca7ee26 100644 --- a/src/testutils/mockNetworkProvider.ts +++ b/src/testutils/mockNetworkProvider.ts @@ -35,6 +35,8 @@ export class MockNetworkProvider implements INetworkProvider { private readonly queryContractResponders: QueryContractResponder[] = []; private readonly getTransactionResponders: GetTransactionResponder[] = []; + mockTransactionCostResponse: TransactionCostResponse; + constructor() { this.transactions = new Map(); this.accounts = new Map(); @@ -57,6 +59,8 @@ export class MockNetworkProvider implements INetworkProvider { balance: createAccountBalance(300), }), ); + + this.mockTransactionCostResponse = new TransactionCostResponse(); } getBlock(): Promise { throw new Error("Method not implemented."); @@ -77,9 +81,11 @@ export class MockNetworkProvider implements INetworkProvider { ): Promise { throw new Error("Method not implemented."); } - estimateTransactionCost(_tx: Transaction): Promise { - throw new Error("Method not implemented."); + + async estimateTransactionCost(_tx: Transaction): Promise { + return this.mockTransactionCostResponse; } + awaitTransactionOnCondition( _transactionHash: string, _condition: (account: TransactionOnNetwork) => boolean, diff --git a/src/testutils/utils.ts b/src/testutils/utils.ts index 6c185c6bb..2d1b2c419 100644 --- a/src/testutils/utils.ts +++ b/src/testutils/utils.ts @@ -1,41 +1,10 @@ import * as fs from "fs"; import { PathLike } from "fs"; import { resolve } from "path"; -import { Abi, Code, SmartContract, TypedValue } from "../abi"; -import { Account } from "../accounts"; -import { Transaction } from "../core/transaction"; +import { Abi } from "../abi"; import { getAxios } from "../core/utils"; -export async function prepareDeployment(obj: { - deployer: Account; - contract: SmartContract; - codePath: string; - initArguments: TypedValue[]; - gasLimit: bigint; - chainID: string; -}): Promise { - let contract = obj.contract; - let deployer = obj.deployer; - - let transaction = obj.contract.deploy({ - code: await loadContractCode(obj.codePath), - gasLimit: obj.gasLimit, - initArguments: obj.initArguments, - chainID: obj.chainID, - deployer: deployer.address, - }); - - let nonce = deployer.getNonceThenIncrement(); - let contractAddress = SmartContract.computeAddress(deployer.address, nonce); - transaction.nonce = nonce; - transaction.sender = deployer.address; - contract.setAddress(contractAddress); - transaction.signature = await deployer.signTransaction(transaction); - - return transaction; -} - -export async function loadContractCode(path: PathLike): Promise { +export async function loadContractCode(path: PathLike): Promise { if (isOnBrowserTests()) { const axios = await getAxios(); let response: any = await axios.default.get(path.toString(), { @@ -46,13 +15,12 @@ export async function loadContractCode(path: PathLike): Promise { }, }); - let buffer = Buffer.from(response.data); - return Code.fromBuffer(buffer); + return Buffer.from(response.data); } // Load from file. let buffer: Buffer = await fs.promises.readFile(path); - return Code.fromBuffer(buffer); + return buffer; } export async function loadAbiRegistry(path: PathLike): Promise { diff --git a/src/tokenManagement/tokenManagementController.ts b/src/tokenManagement/tokenManagementController.ts index c58f11ce3..b0d0f3c54 100644 --- a/src/tokenManagement/tokenManagementController.ts +++ b/src/tokenManagement/tokenManagementController.ts @@ -3,6 +3,7 @@ import { BaseController, BaseControllerInput, IAccount, + IGasLimitEstimator, Transaction, TransactionOnNetwork, TransactionsFactoryConfig, @@ -18,10 +19,15 @@ export class TokenManagementController extends BaseController { private transactionAwaiter: TransactionWatcher; private parser: TokenManagementTransactionsOutcomeParser; - constructor(options: { chainID: string; networkProvider: INetworkProvider }) { + constructor(options: { + chainID: string; + networkProvider: INetworkProvider; + gasLimitEstimator?: IGasLimitEstimator; + }) { super(); this.factory = new TokenManagementTransactionsFactory({ config: new TransactionsFactoryConfig({ chainID: options.chainID }), + gasLimitEstimator: options.gasLimitEstimator, }); this.transactionAwaiter = new TransactionWatcher(options.networkProvider); this.parser = new TokenManagementTransactionsOutcomeParser(); @@ -32,7 +38,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.IssueFungibleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForIssuingFungible(sender.address, options); + const transaction = await this.factory.createTransactionForIssuingFungible(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -58,7 +64,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.IssueSemiFungibleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForIssuingSemiFungible(sender.address, options); + const transaction = await this.factory.createTransactionForIssuingSemiFungible(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -84,7 +90,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.IssueNonFungibleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForIssuingNonFungible(sender.address, options); + const transaction = await this.factory.createTransactionForIssuingNonFungible(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -110,7 +116,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.RegisterMetaESDTInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForRegisteringMetaESDT(sender.address, options); + const transaction = await this.factory.createTransactionForRegisteringMetaESDT(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -136,7 +142,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.RegisterRolesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForRegisteringAndSettingRoles(sender.address, options); + const transaction = await this.factory.createTransactionForRegisteringAndSettingRoles(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -162,7 +168,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.BurnRoleGloballyInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingBurnRoleGlobally(sender.address, options); + const transaction = await this.factory.createTransactionForSettingBurnRoleGlobally(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -188,7 +194,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.BurnRoleGloballyInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingBurnRoleGlobally(sender.address, options); + const transaction = await this.factory.createTransactionForUnsettingBurnRoleGlobally(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -214,7 +220,10 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.FungibleSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingSpecialRoleOnFungibleToken(sender.address, options); + const transaction = await this.factory.createTransactionForSettingSpecialRoleOnFungibleToken( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -240,7 +249,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UnsetFungibleSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingSpecialRoleOnFungibleToken( + const transaction = await this.factory.createTransactionForUnsettingSpecialRoleOnFungibleToken( sender.address, options, ); @@ -260,7 +269,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SemiFungibleSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingSpecialRoleOnSemiFungibleToken( + const transaction = await this.factory.createTransactionForSettingSpecialRoleOnSemiFungibleToken( sender.address, options, ); @@ -289,7 +298,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UnsetSemiFungibleSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingSpecialRoleOnSemiFungibleToken( + const transaction = await this.factory.createTransactionForUnsettingSpecialRoleOnSemiFungibleToken( sender.address, options, ); @@ -309,7 +318,10 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SemiFungibleSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingSpecialRoleOnMetaESDT(sender.address, options); + const transaction = await this.factory.createTransactionForSettingSpecialRoleOnMetaESDT( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -335,7 +347,10 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UnsetSemiFungibleSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingSpecialRoleOnMetaESDT(sender.address, options); + const transaction = await this.factory.createTransactionForUnsettingSpecialRoleOnMetaESDT( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -352,7 +367,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingSpecialRoleOnNonFungibleToken( + const transaction = await this.factory.createTransactionForSettingSpecialRoleOnNonFungibleToken( sender.address, options, ); @@ -381,7 +396,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UnsetSpecialRoleInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnsettingSpecialRoleOnNonFungibleToken( + const transaction = await this.factory.createTransactionForUnsettingSpecialRoleOnNonFungibleToken( sender.address, options, ); @@ -401,7 +416,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.MintInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForCreatingNFT(sender.address, options); + const transaction = await this.factory.createTransactionForCreatingNFT(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -427,7 +442,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.PausingInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForPausing(sender.address, options); + const transaction = await this.factory.createTransactionForPausing(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -453,7 +468,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.PausingInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnpausing(sender.address, options); + const transaction = await this.factory.createTransactionForUnpausing(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -479,7 +494,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.ManagementInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForFreezing(sender.address, options); + const transaction = await this.factory.createTransactionForFreezing(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -505,7 +520,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.ManagementInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUnfreezing(sender.address, options); + const transaction = await this.factory.createTransactionForUnfreezing(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -531,7 +546,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.ManagementInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForWiping(sender.address, options); + const transaction = await this.factory.createTransactionForWiping(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -557,7 +572,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.LocalMintInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForLocalMint(sender.address, options); + const transaction = await this.factory.createTransactionForLocalMint(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -583,7 +598,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.LocalBurnInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForLocalBurning(sender.address, options); + const transaction = await this.factory.createTransactionForLocalBurning(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -609,7 +624,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UpdateAttributesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUpdatingAttributes(sender.address, options); + const transaction = await this.factory.createTransactionForUpdatingAttributes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -635,7 +650,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UpdateQuantityInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForAddingQuantity(sender.address, options); + const transaction = await this.factory.createTransactionForAddingQuantity(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -661,7 +676,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UpdateQuantityInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForBurningQuantity(sender.address, options); + const transaction = await this.factory.createTransactionForBurningQuantity(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -687,7 +702,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.ModifyRoyaltiesInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForModifyingRoyalties(sender.address, options); + const transaction = await this.factory.createTransactionForModifyingRoyalties(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -713,7 +728,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SetNewUriInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForSettingNewUris(sender.address, options); + const transaction = await this.factory.createTransactionForSettingNewUris(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -739,7 +754,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SetNewUriInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForModifyingCreator(sender.address, options); + const transaction = await this.factory.createTransactionForModifyingCreator(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -765,7 +780,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SetNewUriInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForModifyingCreator(sender.address, options); + const transaction = await this.factory.createTransactionForModifyingCreator(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -791,7 +806,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SetNewUriInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForMetadataRecreate(sender.address, options); + const transaction = await this.factory.createTransactionForMetadataRecreate(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -817,7 +832,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.SetNewUriInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForChangingTokenToDynamic(sender.address, options); + const transaction = await this.factory.createTransactionForChangingTokenToDynamic(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -843,7 +858,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.UpdateTokenIDInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForUpdatingTokenId(sender.address, options); + const transaction = await this.factory.createTransactionForUpdatingTokenId(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -865,7 +880,7 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.RegisteringDynamicTokenInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForRegisteringDynamicToken(sender.address, options); + const transaction = await this.factory.createTransactionForRegisteringDynamicToken(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -891,7 +906,10 @@ export class TokenManagementController extends BaseController { nonce: bigint, options: resources.RegisteringDynamicTokenInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForRegisteringDynamicAndSettingRoles(sender.address, options); + const transaction = await this.factory.createTransactionForRegisteringDynamicAndSettingRoles( + sender.address, + options, + ); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); diff --git a/src/tokenManagement/tokenManagementTransactionFactory.spec.ts b/src/tokenManagement/tokenManagementTransactionFactory.spec.ts index 7f13ebd87..741179bc9 100644 --- a/src/tokenManagement/tokenManagementTransactionFactory.spec.ts +++ b/src/tokenManagement/tokenManagementTransactionFactory.spec.ts @@ -15,8 +15,8 @@ describe("test token management transactions factory", () => { tokenManagementFactory = new TokenManagementTransactionsFactory({ config: config }); }); - it("should create 'Transaction' for registering and setting roles", () => { - const transaction = tokenManagementFactory.createTransactionForRegisteringAndSettingRoles(frank.address, { + it("should create 'Transaction' for registering and setting roles", async () => { + const transaction = await tokenManagementFactory.createTransactionForRegisteringAndSettingRoles(frank.address, { tokenName: "TEST", tokenTicker: "TEST", tokenType: "FNG", @@ -33,8 +33,8 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.gasLimit, 60125000n); }); - it("should create 'Transaction' for issuing fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForIssuingFungible(frank.address, { + it("should create 'Transaction' for issuing fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForIssuingFungible(frank.address, { tokenName: "FRANK", tokenTicker: "FRANK", initialSupply: 100n, @@ -58,8 +58,8 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.value, config.issueCost); }); - it("should create 'Transaction' for issuing semi-fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForIssuingSemiFungible(frank.address, { + it("should create 'Transaction' for issuing semi-fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForIssuingSemiFungible(frank.address, { tokenName: "FRANK", tokenTicker: "FRANK", canFreeze: true, @@ -82,8 +82,8 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.value, config.issueCost); }); - it("should create 'Transaction' for issuing non-fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForIssuingNonFungible(frank.address, { + it("should create 'Transaction' for issuing non-fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForIssuingNonFungible(frank.address, { tokenName: "FRANK", tokenTicker: "FRANK", canFreeze: true, @@ -106,8 +106,8 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.value, config.issueCost); }); - it("should create 'Transaction' for registering metaEsdt", () => { - const transaction = tokenManagementFactory.createTransactionForRegisteringMetaESDT(frank.address, { + it("should create 'Transaction' for registering metaEsdt", async () => { + const transaction = await tokenManagementFactory.createTransactionForRegisteringMetaESDT(frank.address, { tokenName: "FRANK", tokenTicker: "FRANK", numDecimals: 10n, @@ -131,8 +131,8 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.value, config.issueCost); }); - it("should create 'Transaction' for setting special role on fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken( + it("should create 'Transaction' for setting special role on fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken( frank.address, { user: grace.address, @@ -154,8 +154,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for unsetting special role on fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForUnsettingSpecialRoleOnFungibleToken( + it("should create 'Transaction' for unsetting special role on fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForUnsettingSpecialRoleOnFungibleToken( frank.address, { user: grace.address, @@ -177,8 +177,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for setting all special roles on fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken( + it("should create 'Transaction' for setting all special roles on fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken( frank.address, { user: grace.address, @@ -200,8 +200,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for setting special role on non-fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnNonFungibleToken( + it("should create 'Transaction' for setting special role on non-fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForSettingSpecialRoleOnNonFungibleToken( frank.address, { user: grace.address, @@ -227,8 +227,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for unsetting special role on non-fungible token", () => { - const transaction = tokenManagementFactory.createTransactionForUnsettingSpecialRoleOnNonFungibleToken( + it("should create 'Transaction' for unsetting special role on non-fungible token", async () => { + const transaction = await tokenManagementFactory.createTransactionForUnsettingSpecialRoleOnNonFungibleToken( frank.address, { user: grace.address, @@ -253,8 +253,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for creating nft", () => { - const transaction = tokenManagementFactory.createTransactionForCreatingNFT(grace.address, { + it("should create 'Transaction' for creating nft", async () => { + const transaction = await tokenManagementFactory.createTransactionForCreatingNFT(grace.address, { tokenIdentifier: "FRANK-aa9e8d", initialQuantity: 1n, name: "test", @@ -273,8 +273,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for modifying royalties", () => { - const transaction = tokenManagementFactory.createTransactionForModifyingRoyalties(grace.address, { + it("should create 'Transaction' for modifying royalties", async () => { + const transaction = await tokenManagementFactory.createTransactionForModifyingRoyalties(grace.address, { tokenIdentifier: "TEST-123456", tokenNonce: 1n, newRoyalties: 1234n, @@ -287,8 +287,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60125000n); }); - it("should create 'Transaction' for setting new URIs", () => { - const transaction = tokenManagementFactory.createTransactionForSettingNewUris(grace.address, { + it("should create 'Transaction' for setting new URIs", async () => { + const transaction = await tokenManagementFactory.createTransactionForSettingNewUris(grace.address, { tokenIdentifier: "TEST-123456", tokenNonce: 1n, newUris: ["firstURI", "secondURI"], @@ -304,8 +304,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60164000n); }); - it("should create 'Transaction' for modifying creator", () => { - const transaction = tokenManagementFactory.createTransactionForModifyingCreator(grace.address, { + it("should create 'Transaction' for modifying creator", async () => { + const transaction = await tokenManagementFactory.createTransactionForModifyingCreator(grace.address, { tokenIdentifier: "TEST-123456", tokenNonce: 1n, }); @@ -317,8 +317,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60114500n); }); - it("should create 'Transaction' for updating metadata", () => { - const transaction = tokenManagementFactory.createTransactionForUpdatingMetadata(grace.address, { + it("should create 'Transaction' for updating metadata", async () => { + const transaction = await tokenManagementFactory.createTransactionForUpdatingMetadata(grace.address, { tokenIdentifier: "TEST-123456", tokenNonce: 1n, newTokenName: "Test", @@ -340,8 +340,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60218000n); }); - it("should create 'Transaction' for recreating metadata", () => { - const transaction = tokenManagementFactory.createTransactionForMetadataRecreate(grace.address, { + it("should create 'Transaction' for recreating metadata", async () => { + const transaction = await tokenManagementFactory.createTransactionForMetadataRecreate(grace.address, { tokenIdentifier: "TEST-123456", tokenNonce: 1n, newTokenName: "Test", @@ -363,8 +363,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60221000n); }); - it("should create 'Transaction' for changing to dynamic", () => { - const transaction = tokenManagementFactory.createTransactionForChangingTokenToDynamic(grace.address, { + it("should create 'Transaction' for changing to dynamic", async () => { + const transaction = await tokenManagementFactory.createTransactionForChangingTokenToDynamic(grace.address, { tokenIdentifier: "TEST-123456", }); @@ -375,8 +375,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60107000n); }); - it("should create 'Transaction' for updating token id", () => { - const transaction = tokenManagementFactory.createTransactionForUpdatingTokenId(grace.address, { + it("should create 'Transaction' for updating token id", async () => { + const transaction = await tokenManagementFactory.createTransactionForUpdatingTokenId(grace.address, { tokenIdentifier: "TEST-123456", }); @@ -387,8 +387,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60104000n); }); - it("should create 'Transaction' for registering dynamic", () => { - const transaction = tokenManagementFactory.createTransactionForRegisteringDynamicToken(grace.address, { + it("should create 'Transaction' for registering dynamic", async () => { + const transaction = await tokenManagementFactory.createTransactionForRegisteringDynamicToken(grace.address, { tokenName: "Test", tokenTicker: "TEST-123456", tokenType: "FNG", @@ -401,8 +401,8 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60131000n); }); - it("should create 'Transaction' for registering and setting all roles", () => { - const transaction = tokenManagementFactory.createTransactionForRegisteringDynamicAndSettingRoles( + it("should create 'Transaction' for registering and setting all roles", async () => { + const transaction = await tokenManagementFactory.createTransactionForRegisteringDynamicAndSettingRoles( grace.address, { tokenName: "Test", diff --git a/src/tokenManagement/tokenManagementTransactionsFactory.ts b/src/tokenManagement/tokenManagementTransactionsFactory.ts index 63884ee5d..fe458e6b6 100644 --- a/src/tokenManagement/tokenManagementTransactionsFactory.ts +++ b/src/tokenManagement/tokenManagementTransactionsFactory.ts @@ -1,10 +1,11 @@ import { AddressValue, ArgSerializer, BigUIntValue, BytesValue, StringValue } from "../abi"; +import { IGasLimitEstimator } from "../core"; import { Address } from "../core/address"; +import { BaseFactory } from "../core/baseFactory"; import { ESDT_CONTRACT_ADDRESS_HEX } from "../core/constants"; import { ErrBadUsage } from "../core/errors"; import { Logger } from "../core/logger"; import { Transaction } from "../core/transaction"; -import { TransactionBuilder } from "../core/transactionBuilder"; import * as resources from "./resources"; interface IConfig { @@ -39,14 +40,15 @@ interface IConfig { /** * Use this class to create token management transactions like issuing ESDTs, creating NFTs, setting roles, etc. */ -export class TokenManagementTransactionsFactory { +export class TokenManagementTransactionsFactory extends BaseFactory { private readonly config: IConfig; private readonly argSerializer: ArgSerializer; private readonly trueAsString: string; private readonly falseAsString: string; private readonly esdtContractAddress: Address; - constructor(options: { config: IConfig }) { + constructor(options: { config: IConfig; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); this.config = options.config; this.argSerializer = new ArgSerializer(); this.trueAsString = "true"; @@ -54,7 +56,10 @@ export class TokenManagementTransactionsFactory { this.esdtContractAddress = Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, this.config.addressHrp); } - createTransactionForIssuingFungible(sender: Address, options: resources.IssueFungibleInput): Transaction { + async createTransactionForIssuingFungible( + sender: Address, + options: resources.IssueFungibleInput, + ): Promise { this.notifyAboutUnsettingBurnRoleGlobally(); const args = [ @@ -78,18 +83,24 @@ export class TokenManagementTransactionsFactory { const dataParts = ["issue", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitIssue, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitIssue); + + return transaction; } - createTransactionForIssuingSemiFungible(sender: Address, options: resources.IssueSemiFungibleInput): Transaction { + async createTransactionForIssuingSemiFungible( + sender: Address, + options: resources.IssueSemiFungibleInput, + ): Promise { this.notifyAboutUnsettingBurnRoleGlobally(); const args = [ @@ -113,18 +124,24 @@ export class TokenManagementTransactionsFactory { const dataParts = ["issueSemiFungible", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitIssue, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitIssue); + + return transaction; } - createTransactionForIssuingNonFungible(sender: Address, options: resources.IssueNonFungibleInput): Transaction { + async createTransactionForIssuingNonFungible( + sender: Address, + options: resources.IssueNonFungibleInput, + ): Promise { this.notifyAboutUnsettingBurnRoleGlobally(); const args = [ @@ -148,18 +165,24 @@ export class TokenManagementTransactionsFactory { const dataParts = ["issueNonFungible", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitIssue, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitIssue); + + return transaction; } - createTransactionForRegisteringMetaESDT(sender: Address, options: resources.RegisterMetaESDTInput): Transaction { + async createTransactionForRegisteringMetaESDT( + sender: Address, + options: resources.RegisterMetaESDTInput, + ): Promise { this.notifyAboutUnsettingBurnRoleGlobally(); const args = [ @@ -184,21 +207,24 @@ export class TokenManagementTransactionsFactory { const dataParts = ["registerMetaESDT", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitIssue, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitIssue); + + return transaction; } - createTransactionForRegisteringAndSettingRoles( + async createTransactionForRegisteringAndSettingRoles( sender: Address, options: resources.RegisterRolesInput, - ): Transaction { + ): Promise { this.notifyAboutUnsettingBurnRoleGlobally(); const dataParts = [ @@ -211,59 +237,68 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitIssue, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitIssue); + + return transaction; } - createTransactionForSettingBurnRoleGlobally( + async createTransactionForSettingBurnRoleGlobally( sender: Address, options: resources.BurnRoleGloballyInput, - ): Transaction { + ): Promise { const dataParts = [ "setBurnRoleGlobally", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitToggleBurnRoleGlobally, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitToggleBurnRoleGlobally); + + return transaction; } - createTransactionForUnsettingBurnRoleGlobally( + async createTransactionForUnsettingBurnRoleGlobally( sender: Address, options: resources.BurnRoleGloballyInput, - ): Transaction { + ): Promise { const dataParts = [ "unsetBurnRoleGlobally", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitToggleBurnRoleGlobally, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitToggleBurnRoleGlobally); + + return transaction; } - createTransactionForSettingSpecialRoleOnFungibleToken( + async createTransactionForSettingSpecialRoleOnFungibleToken( sender: Address, options: resources.FungibleSpecialRoleInput, - ): Transaction { + ): Promise { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.addRoleLocalMint ? args.push(new StringValue("ESDTRoleLocalMint")) : 0; @@ -272,20 +307,23 @@ export class TokenManagementTransactionsFactory { const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetSpecialRole, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetSpecialRole); + + return transaction; } - createTransactionForUnsettingSpecialRoleOnFungibleToken( + async createTransactionForUnsettingSpecialRoleOnFungibleToken( sender: Address, options: resources.UnsetFungibleSpecialRoleInput, - ): Transaction { + ): Promise { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.removeRoleLocalMint ? args.push(new StringValue("ESDTRoleLocalMint")) : 0; @@ -294,20 +332,23 @@ export class TokenManagementTransactionsFactory { const dataParts = ["unSetSpecialRole", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetSpecialRole, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetSpecialRole); + + return transaction; } - createTransactionForSettingSpecialRoleOnSemiFungibleToken( + async createTransactionForSettingSpecialRoleOnSemiFungibleToken( sender: Address, options: resources.SemiFungibleSpecialRoleInput, - ): Transaction { + ): Promise { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.addRoleNFTCreate ? args.push(new StringValue("ESDTRoleNFTCreate")) : 0; @@ -322,20 +363,23 @@ export class TokenManagementTransactionsFactory { const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetSpecialRole, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetSpecialRole); + + return transaction; } - createTransactionForUnsettingSpecialRoleOnSemiFungibleToken( + async createTransactionForUnsettingSpecialRoleOnSemiFungibleToken( sender: Address, options: resources.UnsetSemiFungibleSpecialRoleInput, - ): Transaction { + ): Promise { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.removeRoleNFTBurn ? args.push(new StringValue("ESDTRoleNFTBurn")) : 0; @@ -349,34 +393,37 @@ export class TokenManagementTransactionsFactory { const dataParts = ["unSetSpecialRole", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetSpecialRole, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetSpecialRole); + + return transaction; } - createTransactionForSettingSpecialRoleOnMetaESDT( + async createTransactionForSettingSpecialRoleOnMetaESDT( sender: Address, options: resources.SemiFungibleSpecialRoleInput, - ): Transaction { - return this.createTransactionForSettingSpecialRoleOnSemiFungibleToken(sender, options); + ): Promise { + return await this.createTransactionForSettingSpecialRoleOnSemiFungibleToken(sender, options); } - createTransactionForUnsettingSpecialRoleOnMetaESDT( + async createTransactionForUnsettingSpecialRoleOnMetaESDT( sender: Address, options: resources.UnsetSemiFungibleSpecialRoleInput, - ): Transaction { - return this.createTransactionForUnsettingSpecialRoleOnSemiFungibleToken(sender, options); + ): Promise { + return await this.createTransactionForUnsettingSpecialRoleOnSemiFungibleToken(sender, options); } - createTransactionForSettingSpecialRoleOnNonFungibleToken( + async createTransactionForSettingSpecialRoleOnNonFungibleToken( sender: Address, options: resources.SpecialRoleInput, - ): Transaction { + ): Promise { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.addRoleNFTCreate ? args.push(new StringValue("ESDTRoleNFTCreate")) : 0; @@ -391,20 +438,23 @@ export class TokenManagementTransactionsFactory { const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetSpecialRole, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetSpecialRole); + + return transaction; } - createTransactionForUnsettingSpecialRoleOnNonFungibleToken( + async createTransactionForUnsettingSpecialRoleOnNonFungibleToken( sender: Address, options: resources.UnsetSpecialRoleInput, - ): Transaction { + ): Promise { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.removeRoleNFTBurn ? args.push(new StringValue("ESDTRoleNFTBurn")) : 0; @@ -418,17 +468,20 @@ export class TokenManagementTransactionsFactory { const dataParts = ["unSetSpecialRole", ...this.argSerializer.valuesToStrings(args)]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetSpecialRole, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetSpecialRole); + + return transaction; } - createTransactionForCreatingNFT(sender: Address, options: resources.MintInput): Transaction { + async createTransactionForCreatingNFT(sender: Address, options: resources.MintInput): Promise { const dataParts = [ "ESDTNFTCreate", ...this.argSerializer.valuesToStrings([ @@ -446,46 +499,55 @@ export class TokenManagementTransactionsFactory { const nftData = options.name + options.hash + options.attributes + options.uris.join(""); const storageGasLimit = this.config.gasLimitStorePerByte + BigInt(nftData.length); - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtNftCreate + storageGasLimit, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtNftCreate + storageGasLimit); + + return transaction; } - createTransactionForPausing(sender: Address, options: resources.PausingInput): Transaction { + async createTransactionForPausing(sender: Address, options: resources.PausingInput): Promise { const dataParts = ["pause", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)])]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitPausing, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitPausing); + + return transaction; } - createTransactionForUnpausing(sender: Address, options: resources.PausingInput): Transaction { + async createTransactionForUnpausing(sender: Address, options: resources.PausingInput): Promise { const dataParts = [ "unPause", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitPausing, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitPausing); + + return transaction; } - createTransactionForFreezing(sender: Address, options: resources.ManagementInput): Transaction { + async createTransactionForFreezing(sender: Address, options: resources.ManagementInput): Promise { const dataParts = [ "freeze", ...this.argSerializer.valuesToStrings([ @@ -494,17 +556,20 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitFreezing, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitFreezing); + + return transaction; } - createTransactionForUnfreezing(sender: Address, options: resources.ManagementInput): Transaction { + async createTransactionForUnfreezing(sender: Address, options: resources.ManagementInput): Promise { const dataParts = [ "UnFreeze", ...this.argSerializer.valuesToStrings([ @@ -513,17 +578,20 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitFreezing, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitFreezing); + + return transaction; } - createTransactionForWiping(sender: Address, options: resources.ManagementInput): Transaction { + async createTransactionForWiping(sender: Address, options: resources.ManagementInput): Promise { const dataParts = [ "wipe", ...this.argSerializer.valuesToStrings([ @@ -532,17 +600,20 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitWiping, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitWiping); + + return transaction; } - createTransactionForLocalMint(sender: Address, options: resources.LocalMintInput): Transaction { + async createTransactionForLocalMint(sender: Address, options: resources.LocalMintInput): Promise { const dataParts = [ "ESDTLocalMint", ...this.argSerializer.valuesToStrings([ @@ -551,17 +622,20 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtLocalMint, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtLocalMint); + + return transaction; } - createTransactionForLocalBurning(sender: Address, options: resources.LocalBurnInput): Transaction { + async createTransactionForLocalBurning(sender: Address, options: resources.LocalBurnInput): Promise { const dataParts = [ "ESDTLocalBurn", ...this.argSerializer.valuesToStrings([ @@ -570,17 +644,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtLocalBurn, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtLocalBurn); + + return transaction; } - createTransactionForUpdatingAttributes(sender: Address, options: resources.UpdateAttributesInput): Transaction { + async createTransactionForUpdatingAttributes( + sender: Address, + options: resources.UpdateAttributesInput, + ): Promise { const dataParts = [ "ESDTNFTUpdateAttributes", ...this.argSerializer.valuesToStrings([ @@ -590,17 +670,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtNftUpdateAttributes, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtNftUpdateAttributes); + + return transaction; } - createTransactionForAddingQuantity(sender: Address, options: resources.UpdateQuantityInput): Transaction { + async createTransactionForAddingQuantity( + sender: Address, + options: resources.UpdateQuantityInput, + ): Promise { const dataParts = [ "ESDTNFTAddQuantity", ...this.argSerializer.valuesToStrings([ @@ -610,17 +696,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtNftAddQuantity, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtNftAddQuantity); + + return transaction; } - createTransactionForBurningQuantity(sender: Address, options: resources.UpdateQuantityInput): Transaction { + async createTransactionForBurningQuantity( + sender: Address, + options: resources.UpdateQuantityInput, + ): Promise { const dataParts = [ "ESDTNFTBurn", ...this.argSerializer.valuesToStrings([ @@ -630,17 +722,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtNftBurn, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtNftBurn); + + return transaction; } - createTransactionForModifyingRoyalties(sender: Address, options: resources.ModifyRoyaltiesInput): Transaction { + async createTransactionForModifyingRoyalties( + sender: Address, + options: resources.ModifyRoyaltiesInput, + ): Promise { const dataParts = [ "ESDTModifyRoyalties", ...this.argSerializer.valuesToStrings([ @@ -650,17 +748,20 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtModifyRoyalties, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtModifyRoyalties); + + return transaction; } - createTransactionForSettingNewUris(sender: Address, options: resources.SetNewUriInput): Transaction { + async createTransactionForSettingNewUris(sender: Address, options: resources.SetNewUriInput): Promise { if (!options.newUris.length) { throw new ErrBadUsage("No URIs provided"); } @@ -674,17 +775,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitSetNewUris, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitSetNewUris); + + return transaction; } - createTransactionForModifyingCreator(sender: Address, options: resources.ModifyCreatorInput): Transaction { + async createTransactionForModifyingCreator( + sender: Address, + options: resources.ModifyCreatorInput, + ): Promise { const dataParts = [ "ESDTModifyCreator", ...this.argSerializer.valuesToStrings([ @@ -693,17 +800,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtModifyCreator, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtModifyCreator); + + return transaction; } - createTransactionForUpdatingMetadata(sender: Address, options: resources.ManageMetadataInput): Transaction { + async createTransactionForUpdatingMetadata( + sender: Address, + options: resources.ManageMetadataInput, + ): Promise { const dataParts = [ "ESDTMetaDataUpdate", ...this.argSerializer.valuesToStrings([ @@ -717,17 +830,23 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitEsdtMetadataUpdate, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitEsdtMetadataUpdate); + + return transaction; } - createTransactionForMetadataRecreate(sender: Address, options: resources.ManageMetadataInput): Transaction { + async createTransactionForMetadataRecreate( + sender: Address, + options: resources.ManageMetadataInput, + ): Promise { const dataParts = [ "ESDTMetaDataRecreate", ...this.argSerializer.valuesToStrings([ @@ -741,55 +860,67 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: this.config.gasLimitNftMetadataRecreate, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitNftMetadataRecreate); + + return transaction; } - createTransactionForChangingTokenToDynamic( + async createTransactionForChangingTokenToDynamic( sender: Address, options: resources.ChangeTokenToDynamicInput, - ): Transaction { + ): Promise { const dataParts = [ "changeToDynamic", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitNftChangeToDynamic, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitNftChangeToDynamic); + + return transaction; } - createTransactionForUpdatingTokenId(sender: Address, options: resources.UpdateTokenIDInput): Transaction { + async createTransactionForUpdatingTokenId( + sender: Address, + options: resources.UpdateTokenIDInput, + ): Promise { const dataParts = [ "updateTokenID", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitUpdateTokenId, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitUpdateTokenId); + + return transaction; } - createTransactionForRegisteringDynamicToken( + async createTransactionForRegisteringDynamicToken( sender: Address, options: resources.RegisteringDynamicTokenInput, - ): Transaction { + ): Promise { const dataParts = [ "registerDynamic", ...this.argSerializer.valuesToStrings([ @@ -799,21 +930,24 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitRegisterDynamic, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitRegisterDynamic); + + return transaction; } - createTransactionForRegisteringDynamicAndSettingRoles( + async createTransactionForRegisteringDynamicAndSettingRoles( sender: Address, options: resources.RegisteringDynamicTokenInput, - ): Transaction { + ): Promise { const dataParts = [ "registerAndSetAllRolesDynamic", ...this.argSerializer.valuesToStrings([ @@ -823,15 +957,18 @@ export class TokenManagementTransactionsFactory { ]), ]; - return new TransactionBuilder({ - config: this.config, + const transaction = new Transaction({ sender: sender, receiver: this.esdtContractAddress, - dataParts: dataParts, - gasLimit: this.config.gasLimitRegisterDynamic, - addDataMovementGas: true, - amount: this.config.issueCost, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + value: this.config.issueCost, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitRegisterDynamic); + + return transaction; } private notifyAboutUnsettingBurnRoleGlobally() { diff --git a/src/transfers/transferTransactionsFactory.spec.ts b/src/transfers/transferTransactionsFactory.spec.ts index 348d61602..a4be2ea15 100644 --- a/src/transfers/transferTransactionsFactory.spec.ts +++ b/src/transfers/transferTransactionsFactory.spec.ts @@ -16,20 +16,21 @@ describe("test transfer transactions factory", function () { it("should throw error, no token transfer provided", async () => { let transfers: any = []; - assert.throw( - () => { - transferFactory.createTransactionForESDTTokenTransfer(alice, { - receiver: bob, - tokenTransfers: transfers, - }); - }, - ErrBadUsage, - "No token transfer has been provided", - ); + try { + await transferFactory.createTransactionForESDTTokenTransfer(alice, { + receiver: bob, + tokenTransfers: transfers, + }); + + assert.fail("Expected error was not thrown"); + } catch (err) { + assert.instanceOf(err, ErrBadUsage); + assert.match(err.message, /No token transfer has been provided/); + } }); it("should create 'Transaction' for native token transfer without data", async () => { - const transaction = transferFactory.createTransactionForNativeTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForNativeTokenTransfer(alice, { receiver: bob, nativeAmount: 1000000000000000000n, }); @@ -42,7 +43,7 @@ describe("test transfer transactions factory", function () { }); it("should create 'Transaction' for native token transfer with data", async () => { - const transaction = transferFactory.createTransactionForNativeTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForNativeTokenTransfer(alice, { receiver: bob, nativeAmount: 1000000000000000000n, data: Buffer.from("test data"), @@ -59,7 +60,7 @@ describe("test transfer transactions factory", function () { const fooToken = new Token({ identifier: "FOO-123456", nonce: 0n }); const transfer = new TokenTransfer({ token: fooToken, amount: 1000000n }); - const transaction = transferFactory.createTransactionForESDTTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForESDTTokenTransfer(alice, { receiver: bob, tokenTransfers: [transfer], }); @@ -75,7 +76,7 @@ describe("test transfer transactions factory", function () { const nft = new Token({ identifier: "NFT-123456", nonce: 10n }); const transfer = new TokenTransfer({ token: nft, amount: 1n }); - const transaction = transferFactory.createTransactionForESDTTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForESDTTokenTransfer(alice, { receiver: bob, tokenTransfers: [transfer], }); @@ -94,7 +95,7 @@ describe("test transfer transactions factory", function () { const nft = new Token({ identifier: "t0-NFT-123456", nonce: 10n }); const transfer = new TokenTransfer({ token: nft, amount: 1n }); - const transaction = transferFactory.createTransactionForESDTTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForESDTTokenTransfer(alice, { receiver: bob, tokenTransfers: [transfer], }); @@ -116,7 +117,7 @@ describe("test transfer transactions factory", function () { const secondNft = new Token({ identifier: "TEST-987654", nonce: 1n }); const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); - const transaction = transferFactory.createTransactionForESDTTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForESDTTokenTransfer(alice, { receiver: bob, tokenTransfers: [firstTransfer, secondTransfer], }); @@ -130,7 +131,7 @@ describe("test transfer transactions factory", function () { "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@4e46542d313233343536@0a@01@544553542d393837363534@01@01", ); - const secondTransaction = transferFactory.createTransactionForTransfer(alice, { + const secondTransaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, tokenTransfers: [firstTransfer, secondTransfer], }); @@ -145,7 +146,7 @@ describe("test transfer transactions factory", function () { const secondNft = new Token({ identifier: "t0-TEST-987654", nonce: 1n }); const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); - const transaction = transferFactory.createTransactionForESDTTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForESDTTokenTransfer(alice, { receiver: bob, tokenTransfers: [firstTransfer, secondTransfer], }); @@ -159,7 +160,7 @@ describe("test transfer transactions factory", function () { "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@74302d4e46542d313233343536@0a@01@74302d544553542d393837363534@01@01", ); - const secondTransaction = transferFactory.createTransactionForTransfer(alice, { + const secondTransaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, tokenTransfers: [firstTransfer, secondTransfer], }); @@ -168,20 +169,24 @@ describe("test transfer transactions factory", function () { }); it("should fail to create transaction for token transfers", async () => { - assert.throws(() => { + try { const nft = new Token({ identifier: "NFT-123456", nonce: 10n }); const transfer = new TokenTransfer({ token: nft, amount: 1n }); - transferFactory.createTransactionForTransfer(alice, { + await transferFactory.createTransactionForTransfer(alice, { receiver: bob, tokenTransfers: [transfer], data: Buffer.from("test"), }); - }, "Can't set data field when sending esdt tokens"); + + assert.fail("Expected error was not thrown"); + } catch (err) { + assert.match(err.message, /Can't set data field when sending esdt tokens/); + } }); it("should create transaction for native transfers", async () => { - const transaction = transferFactory.createTransactionForTransfer(alice, { + const transaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, nativeAmount: 1000000000000000000n, }); @@ -193,7 +198,7 @@ describe("test transfer transactions factory", function () { }); it("should create transaction for native transfers and set data field", async () => { - const transaction = transferFactory.createTransactionForTransfer(alice, { + const transaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, nativeAmount: 1000000000000000000n, data: Buffer.from("hello"), @@ -207,7 +212,7 @@ describe("test transfer transactions factory", function () { }); it("should create transaction for notarizing", async () => { - const transaction = transferFactory.createTransactionForTransfer(alice, { + const transaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, data: Buffer.from("hello"), }); @@ -225,7 +230,7 @@ describe("test transfer transactions factory", function () { const secondNft = new Token({ identifier: "TEST-987654", nonce: 1n }); const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); - const transaction = transferFactory.createTransactionForTransfer(alice, { + const transaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, nativeAmount: 1000000000000000000n, tokenTransfers: [firstTransfer, secondTransfer], @@ -248,7 +253,7 @@ describe("test transfer transactions factory", function () { const secondNft = new Token({ identifier: "t0-TEST-987654", nonce: 1n }); const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); - const transaction = transferFactory.createTransactionForTransfer(alice, { + const transaction = await transferFactory.createTransactionForTransfer(alice, { receiver: bob, nativeAmount: 1000000000000000000n, tokenTransfers: [firstTransfer, secondTransfer], @@ -268,7 +273,7 @@ describe("test transfer transactions factory", function () { const firstNft = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1000000000000000000n }); - const transaction = transferFactory.createTransactionForESDTTokenTransfer(alice, { + const transaction = await transferFactory.createTransactionForESDTTokenTransfer(alice, { receiver: bob, tokenTransfers: [firstTransfer], }); diff --git a/src/transfers/transferTransactionsFactory.ts b/src/transfers/transferTransactionsFactory.ts index 733a95107..e97077f27 100644 --- a/src/transfers/transferTransactionsFactory.ts +++ b/src/transfers/transferTransactionsFactory.ts @@ -1,10 +1,11 @@ +import { IGasLimitEstimator } from "../core"; import { Address } from "../core/address"; +import { BaseFactory } from "../core/baseFactory"; import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "../core/constants"; import { ErrBadUsage } from "../core/errors"; import { TokenComputer, TokenTransfer } from "../core/tokens"; import { TokenTransfersDataBuilder } from "../core/tokenTransfersDataBuilder"; import { Transaction } from "../core/transaction"; -import { TransactionBuilder } from "../core/transactionBuilder"; import * as resources from "./resources"; const ADDITIONAL_GAS_FOR_ESDT_TRANSFER = 100000; @@ -22,31 +23,42 @@ interface IConfig { /** * Use this class to create transactions for native token transfers (EGLD) or custom tokens transfers (ESDT/NTF/MetaESDT). */ -export class TransferTransactionsFactory { - private readonly config?: IConfig; - private readonly tokenTransfersDataBuilder?: TokenTransfersDataBuilder; - private readonly tokenComputer?: TokenComputer; +export class TransferTransactionsFactory extends BaseFactory { + private readonly config: IConfig; + private readonly tokenTransfersDataBuilder: TokenTransfersDataBuilder; + private readonly tokenComputer: TokenComputer; - constructor(options: { config: IConfig }) { + constructor(options: { config: IConfig; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); this.config = options.config; this.tokenComputer = new TokenComputer(); this.tokenTransfersDataBuilder = new TokenTransfersDataBuilder(); } - createTransactionForNativeTokenTransfer(sender: Address, options: resources.NativeTokenTransferInput): Transaction { + async createTransactionForNativeTokenTransfer( + sender: Address, + options: resources.NativeTokenTransferInput, + ): Promise { const data = options.data || new Uint8Array(); - return new Transaction({ + const transaction = new Transaction({ sender: sender, receiver: options.receiver, - chainID: this.config!.chainID, - gasLimit: this.computeGasForMoveBalance(this.config!, data), + chainID: this.config.chainID, + gasLimit: 0n, data: data, value: options.nativeAmount ?? BigInt(0), }); + + await this.setGasLimit(transaction, undefined, 0n); + + return transaction; } - createTransactionForESDTTokenTransfer(sender: Address, options: resources.CustomTokenTransferInput): Transaction { + async createTransactionForESDTTokenTransfer( + sender: Address, + options: resources.CustomTokenTransferInput, + ): Promise { const numberOfTransfers = options.tokenTransfers.length; if (numberOfTransfers === 0) { @@ -54,7 +66,7 @@ export class TransferTransactionsFactory { } if (numberOfTransfers === 1) { - return this.createSingleESDTTransferTransaction(sender, options); + return await this.createSingleESDTTransferTransaction(sender, options); } const { dataParts, extraGasForTransfer } = this.buildMultiESDTNFTTransferData( @@ -62,17 +74,23 @@ export class TransferTransactionsFactory { options.receiver, ); - return new TransactionBuilder({ - config: this.config!, + const transaction = new Transaction({ sender: sender, receiver: sender, - dataParts: dataParts, - gasLimit: extraGasForTransfer, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, extraGasForTransfer); + + return transaction; } - createTransactionForTransfer(sender: Address, options: resources.CreateTransferTransactionInput): Transaction { + async createTransactionForTransfer( + sender: Address, + options: resources.CreateTransferTransactionInput, + ): Promise { const nativeAmount = options.nativeAmount ?? 0n; let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; const numberOfTokens = tokenTransfers.length; @@ -82,7 +100,7 @@ export class TransferTransactionsFactory { } if ((nativeAmount && numberOfTokens === 0) || options.data) { - return this.createTransactionForNativeTokenTransfer(sender, { + return await this.createTransactionForNativeTokenTransfer(sender, { receiver: options.receiver, nativeAmount: nativeAmount, data: options.data, @@ -94,33 +112,36 @@ export class TransferTransactionsFactory { tokenTransfers.push(nativeTransfer); } - return this.createTransactionForESDTTokenTransfer(sender, { + return await this.createTransactionForESDTTokenTransfer(sender, { receiver: options.receiver, tokenTransfers: tokenTransfers, }); } - private createSingleESDTTransferTransaction( + private async createSingleESDTTransferTransaction( sender: Address, options: { receiver: Address; tokenTransfers: TokenTransfer[]; }, - ): Transaction { + ): Promise { const transfer = options.tokenTransfers[0]; const { dataParts, extraGasForTransfer, receiver } = this.buildTransferData(transfer, { sender, receiver: options.receiver, }); - return new TransactionBuilder({ - config: this.config!, + const transaction = new Transaction({ sender: sender, receiver: receiver, - dataParts: dataParts, - gasLimit: extraGasForTransfer, - addDataMovementGas: true, - }).build(); + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, extraGasForTransfer); + + return transaction; } private buildTransferData(transfer: TokenTransfer, options: { sender: Address; receiver: Address }) { @@ -164,8 +185,4 @@ export class TransferTransactionsFactory { extraGasForTransfer: this.config!.gasLimitESDTNFTTransfer + BigInt(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER), }; } - - private computeGasForMoveBalance(config: IConfig, data: Uint8Array): bigint { - return config.minGasLimit + config.gasLimitPerByte * BigInt(data.length); - } } diff --git a/src/transfers/transfersControllers.ts b/src/transfers/transfersControllers.ts index c7fa5b44c..ffc3b2a1e 100644 --- a/src/transfers/transfersControllers.ts +++ b/src/transfers/transfersControllers.ts @@ -3,6 +3,7 @@ import { BaseController, BaseControllerInput, IAccount, + IGasLimitEstimator, Transaction, TransactionsFactoryConfig, } from "../core"; @@ -12,9 +13,12 @@ import { TransferTransactionsFactory } from "./transferTransactionsFactory"; export class TransfersController extends BaseController { private factory: TransferTransactionsFactory; - constructor(options: { chainID: string }) { + constructor(options: { chainID: string; gasLimitEstimator?: IGasLimitEstimator }) { super(); - this.factory = new TransferTransactionsFactory({ config: new TransactionsFactoryConfig(options) }); + this.factory = new TransferTransactionsFactory({ + config: new TransactionsFactoryConfig(options), + gasLimitEstimator: options.gasLimitEstimator, + }); } async createTransactionForNativeTokenTransfer( @@ -22,7 +26,7 @@ export class TransfersController extends BaseController { nonce: bigint, options: resources.NativeTokenTransferInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForNativeTokenTransfer(sender.address, options); + const transaction = await this.factory.createTransactionForNativeTokenTransfer(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -39,7 +43,7 @@ export class TransfersController extends BaseController { nonce: bigint, options: resources.CustomTokenTransferInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForESDTTokenTransfer(sender.address, options); + const transaction = await this.factory.createTransactionForESDTTokenTransfer(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -56,7 +60,7 @@ export class TransfersController extends BaseController { nonce: bigint, options: resources.CreateTransferTransactionInput & BaseControllerInput, ): Promise { - const transaction = this.factory.createTransactionForTransfer(sender.address, options); + const transaction = await this.factory.createTransactionForTransfer(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); diff --git a/src/wallet/crypto/pubkeyEncrypt.spec.ts b/src/wallet/crypto/pubkeyEncrypt.spec.ts index f550cd2f2..d4b018e57 100644 --- a/src/wallet/crypto/pubkeyEncrypt.spec.ts +++ b/src/wallet/crypto/pubkeyEncrypt.spec.ts @@ -17,7 +17,7 @@ describe("test address", () => { encryptedDataOfAliceForBob = PubkeyEncryptor.encrypt( sensitiveData, - new UserPublicKey(bob.address.pubkey()), + new UserPublicKey(bob.address.getPublicKey()), new UserSecretKey(alice.secretKey), ); }); diff --git a/src/wallet/pem.spec.ts b/src/wallet/pem.spec.ts index 34a3a9ef4..457e7db0a 100644 --- a/src/wallet/pem.spec.ts +++ b/src/wallet/pem.spec.ts @@ -40,14 +40,14 @@ MWU0YzE3YTRjZDc3NDI0Nw== it("should parse multi-key PEM files", () => { // The user PEM files encode both the seed and the pubkey in their payloads. - let payloadAlice = Buffer.from(alice.secretKeyHex + alice.address.hex()).toString("base64"); - let payloadBob = Buffer.from(bob.secretKeyHex + bob.address.hex()).toString("base64"); - let payloadCarol = Buffer.from(carol.secretKeyHex + carol.address.hex()).toString("base64"); + let payloadAlice = Buffer.from(alice.secretKeyHex + alice.address.toHex()).toString("base64"); + let payloadBob = Buffer.from(bob.secretKeyHex + bob.address.toHex()).toString("base64"); + let payloadCarol = Buffer.from(carol.secretKeyHex + carol.address.toHex()).toString("base64"); let expected = [ - Buffer.concat([alice.secretKey, alice.address.pubkey()]), - Buffer.concat([bob.secretKey, bob.address.pubkey()]), - Buffer.concat([carol.secretKey, carol.address.pubkey()]), + Buffer.concat([alice.secretKey, alice.address.getPublicKey()]), + Buffer.concat([bob.secretKey, bob.address.getPublicKey()]), + Buffer.concat([carol.secretKey, carol.address.getPublicKey()]), ]; let trivialContent = `-----BEGIN PRIVATE KEY for alice diff --git a/src/wallet/users.spec.ts b/src/wallet/users.spec.ts index b24138326..5fead15bb 100644 --- a/src/wallet/users.spec.ts +++ b/src/wallet/users.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import path from "path"; import { Account } from "../accounts"; -import { Address, ErrBadMnemonicEntropy, ErrInvariantFailed, Message, Transaction } from "../core"; +import { Address, ErrBadMnemonicEntropy, ErrInvariantFailed, Message, Transaction, TransactionComputer } from "../core"; import { DummyMnemonicOf12Words, loadMnemonic, @@ -117,15 +117,15 @@ describe("test user wallets", () => { let secretKey: UserSecretKey; secretKey = new UserSecretKey(Buffer.from(alice.secretKeyHex, "hex")); - assert.equal(secretKey.generatePublicKey().hex(), alice.address.hex()); + assert.equal(secretKey.generatePublicKey().hex(), alice.address.toHex()); assert.deepEqual(secretKey.generatePublicKey().toAddress(), alice.address); secretKey = new UserSecretKey(Buffer.from(bob.secretKeyHex, "hex")); - assert.equal(secretKey.generatePublicKey().hex(), bob.address.hex()); + assert.equal(secretKey.generatePublicKey().hex(), bob.address.toHex()); assert.deepEqual(secretKey.generatePublicKey().toAddress(), bob.address); secretKey = new UserSecretKey(Buffer.from(carol.secretKeyHex, "hex")); - assert.equal(secretKey.generatePublicKey().hex(), carol.address.hex()); + assert.equal(secretKey.generatePublicKey().hex(), carol.address.toHex()); assert.deepEqual(secretKey.generatePublicKey().toAddress(), carol.address); }); @@ -299,6 +299,7 @@ describe("test user wallets", () => { "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf", ).generatePublicKey(), ); + const transactionComputer = new TransactionComputer(); // With data field let transaction = new Transaction({ @@ -312,14 +313,11 @@ describe("test user wallets", () => { chainID: "1", }); - let serialized = transaction.serializeForSigning(); + let serialized = transactionComputer.computeBytesForSigning(transaction); let signature = await signer.sign(serialized); assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); - assert.deepEqual( - serialized.toString(), - `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":2}`, - ); + assert.equal( Buffer.from(signature).toString("hex"), "a5db62c6186612d44094f83576aa6a664299315fb6e42d0c17a40e9cd33efa9a9df8b76943aeac7dceaff3d78a16a7414c914f03f7a88e786c2cf939eb111c06", @@ -337,14 +335,11 @@ describe("test user wallets", () => { chainID: "1", }); - serialized = transaction.serializeForSigning(); + serialized = transactionComputer.computeBytesForSigning(transaction); signature = await signer.sign(serialized); assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); - assert.equal( - serialized.toString(), - `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":2}`, - ); + assert.equal( Buffer.from(signature).toString("hex"), "024f007f7eae87141b34708e33afd66c85a49ea8c8422e55292832ee870f879cdc033d2511c174d0f2ed62799b9f597c4a8399309578a258f558131d74374f0d", @@ -362,6 +357,7 @@ describe("test user wallets", () => { ).generatePublicKey(), ); let guardianSigner = new UserSigner(UserSecretKey.fromPem(bob.pemFileText)); + const transactionComputer = new TransactionComputer(); // With data field let transaction = new Transaction({ @@ -378,14 +374,10 @@ describe("test user wallets", () => { version: 2, }); - let serialized = transaction.serializeForSigning(); + let serialized = transactionComputer.computeBytesForSigning(transaction); let signature = await signer.sign(serialized); let guardianSignature = await guardianSigner.sign(serialized); - assert.deepEqual( - serialized.toString(), - `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":2,"options":2,"guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"}`, - ); assert.equal( Buffer.from(signature).toString("hex"), "fa067dc9508ec9df04896665fc9c9e3e7e9cbdc6577c10d56128e3c891ea502572be637bd7cdfb466779cee3e208a2be1f32b0267af1710a6532848e5e5e6f0d", @@ -410,14 +402,10 @@ describe("test user wallets", () => { version: 2, }); - serialized = transaction.serializeForSigning(); + serialized = transactionComputer.computeBytesForSigning(transaction); signature = await signer.sign(serialized); guardianSignature = await guardianSigner.sign(serialized); - assert.equal( - serialized.toString(), - `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":2,"options":2,"guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"}`, - ); assert.equal( Buffer.from(signature).toString("hex"), "50d61a408cf032b3e70b15ecc313dbea43e35a1b33ea89aadb42b25a672d3427147bcda0d911be539629fcd3183c22b30f8ac30023abb230b13abf2cd1befd04", @@ -431,6 +419,7 @@ describe("test user wallets", () => { it("should sign transactions using PEM files", async () => { const signer = UserSigner.fromPem(alice.pemFileText); + const transactionComputer = new TransactionComputer(); const transaction = new Transaction({ nonce: 0n, @@ -443,7 +432,7 @@ describe("test user wallets", () => { chainID: "1", }); - const serialized = transaction.serializeForSigning(); + const serialized = transactionComputer.computeBytesForSigning(transaction); const signature = await signer.sign(serialized); assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized)));