diff --git a/src/adapters/queryRunnerAdapter.ts b/src/adapters/queryRunnerAdapter.ts index b78840c77..db673de4c 100644 --- a/src/adapters/queryRunnerAdapter.ts +++ b/src/adapters/queryRunnerAdapter.ts @@ -23,7 +23,7 @@ export class QueryRunnerAdapter { } async runQuery(query: SmartContractQuery): Promise { - const legacyQuery: IQuery = { + const adaptedQuery: IQuery = { address: Address.fromBech32(query.contract), caller: query.caller ? Address.fromBech32(query.caller) : undefined, func: query.function, @@ -31,14 +31,12 @@ export class QueryRunnerAdapter { getEncodedArguments: () => query.arguments.map((arg) => Buffer.from(arg).toString("hex")), }; - const legacyQueryResponse = await this.networkProvider.queryContract(legacyQuery); - const queryResponse = new SmartContractQueryResponse({ + const adaptedQueryResponse = await this.networkProvider.queryContract(adaptedQuery); + return new SmartContractQueryResponse({ function: query.function, - returnCode: legacyQueryResponse.returnCode.toString(), - returnMessage: legacyQueryResponse.returnMessage, - returnDataParts: legacyQueryResponse.getReturnDataParts(), + returnCode: adaptedQueryResponse.returnCode.toString(), + returnMessage: adaptedQueryResponse.returnMessage, + returnDataParts: adaptedQueryResponse.getReturnDataParts(), }); - - return queryResponse; } } diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 57db4e3c5..16247af5f 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -4,17 +4,26 @@ import { loadAbiRegistry, loadTestWallets, prepareDeployment, TestWallet } from import { ContractController } from "../testutils/contractController"; import { createLocalnetProvider } from "../testutils/networkProviders"; import { Transaction } from "../transaction"; +import { TransactionComputer } from "../transactionComputer"; import { Interaction } from "./interaction"; import { ReturnCode } from "./returnCode"; import { SmartContract } from "./smartContract"; - +import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig"; +import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; +import { promises } from "fs"; +import { ResultsParser } from "./resultsParser"; +import { TransactionWatcher } from "../transactionWatcher"; +import { SmartContractQueriesController } from "../smartContractQueriesController"; +import { QueryRunnerAdapter } from "../adapters/queryRunnerAdapter"; describe("test smart contract interactor", function () { let provider = createLocalnetProvider(); let alice: TestWallet; + let resultsParser: ResultsParser; before(async function () { ({ alice } = await loadTestWallets()); + resultsParser = new ResultsParser(); }); it("should interact with 'answer' (local testnet)", async function () { @@ -34,16 +43,21 @@ describe("test smart contract interactor", function () { codePath: "src/testdata/answer.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); - let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + let { + bundle: { returnCode }, + } = await controller.deploy(deployTransaction); assert.isTrue(returnCode.isSuccess()); - const interaction = contract.methods.getUltimateAnswer() - .withGasLimit(3000000) - .withChainID(network.ChainID) - .withSender(alice.address); + const interaction = ( + contract.methods + .getUltimateAnswer() + .withGasLimit(3000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); // Query let queryResponseBundle = await controller.query(interaction); @@ -61,10 +75,7 @@ describe("test smart contract interactor", function () { await provider.sendTransaction(transaction); // Execute, and wait for execution - transaction = interaction - .withSender(alice.address) - .useThenIncrementNonceOf(alice.account) - .buildTransaction(); + transaction = interaction.withSender(alice.address).useThenIncrementNonceOf(alice.account).buildTransaction(); await signTransaction({ transaction: transaction, wallet: alice }); let { bundle: executionResultsBundle } = await controller.execute(interaction, transaction); @@ -74,6 +85,105 @@ describe("test smart contract interactor", function () { assert.isTrue(executionResultsBundle.returnCode.equals(ReturnCode.Ok)); }); + it("should interact with 'answer' (local testnet) using the SmartContractTransactionsFactory", async function () { + this.timeout(80000); + + let abiRegistry = await loadAbiRegistry("src/testdata/answer.abi.json"); + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + + const bytecode = await promises.readFile("src/testdata/answer.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionCompletionAwaiter = new TransactionWatcher({ + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }); + + const deployTxHash = await provider.sendTransaction(deployTransaction); + let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); + const untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(untypedBundle.returnCode.isSuccess()); + + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const queryController = new SmartContractQueriesController({ abi: abiRegistry, queryRunner: queryRunner }); + + const query = queryController.createQuery({ + contract: contractAddress.bech32(), + caller: alice.address.bech32(), + 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({ + sender: alice.address, + contract: contractAddress, + function: "getUltimateAnswer", + gasLimit: 3000000n, + }); + transaction.nonce = BigInt(alice.account.nonce.valueOf()); + transaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transaction)), + ); + + alice.account.incrementNonce(); + + await provider.sendTransaction(transaction); + + // Execute, and wait for execution + transaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "getUltimateAnswer", + gasLimit: 3000000n, + }); + transaction.nonce = BigInt(alice.account.nonce.valueOf()); + transaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transaction)), + ); + + alice.account.incrementNonce(); + + const executeTxHash = await provider.sendTransaction(transaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(executeTxHash); + const typedBundle = resultsParser.parseOutcome( + transactionOnNetwork, + abiRegistry.getEndpoint("getUltimateAnswer"), + ); + + assert.lengthOf(typedBundle.values, 1); + assert.deepEqual(typedBundle.firstValue!.valueOf(), new BigNumber(42)); + assert.isTrue(typedBundle.returnCode.equals(ReturnCode.Ok)); + }); + it("should interact with 'counter' (local testnet)", async function () { this.timeout(120000); @@ -91,10 +201,12 @@ describe("test smart contract interactor", function () { codePath: "src/testdata/counter.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); - let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + let { + bundle: { returnCode }, + } = await controller.deploy(deployTransaction); assert.isTrue(returnCode.isSuccess()); let getInteraction = contract.methods.get(); @@ -114,7 +226,9 @@ describe("test smart contract interactor", function () { // Increment, wait for execution. let incrementTransaction = incrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await signTransaction({ transaction: incrementTransaction, wallet: alice }); - let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction); + let { + bundle: { firstValue: valueAfterIncrement }, + } = await controller.execute(incrementInteraction, incrementTransaction); assert.deepEqual(valueAfterIncrement!.valueOf(), new BigNumber(2)); // Decrement twice. Wait for execution of the second transaction. @@ -124,10 +238,111 @@ describe("test smart contract interactor", function () { decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await signTransaction({ transaction: decrementTransaction, wallet: alice }); - let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); + let { + bundle: { firstValue: valueAfterDecrement }, + } = await controller.execute(decrementInteraction, decrementTransaction); assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(0)); }); + it("should interact with 'counter' (local testnet) using the SmartContractTransactionsFactory", async function () { + this.timeout(120000); + + let abiRegistry = await loadAbiRegistry("src/testdata/counter.abi.json"); + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionCompletionAwaiter = new TransactionWatcher({ + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }); + + const deployTxHash = await provider.sendTransaction(deployTransaction); + let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); + const untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(untypedBundle.returnCode.isSuccess()); + + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const queryController = new SmartContractQueriesController({ abi: abiRegistry, queryRunner: queryRunner }); + + let incrementTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + incrementTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + incrementTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(incrementTransaction)), + ); + + alice.account.incrementNonce(); + + // Query "get()" + const query = queryController.createQuery({ + contract: contractAddress.bech32(), + 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 typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("increment")); + assert.deepEqual(typedBundle.firstValue!.valueOf(), new BigNumber(2)); + + let decrementTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "decrement", + gasLimit: 3000000n, + }); + decrementTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + decrementTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(decrementTransaction)), + ); + + alice.account.incrementNonce(); + + await provider.sendTransaction(decrementTransaction); + + decrementTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + decrementTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(decrementTransaction)), + ); + + const decrementTxHash = await provider.sendTransaction(decrementTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(decrementTxHash); + typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("decrement")); + }); + it("should interact with 'lottery-esdt' (local testnet)", async function () { this.timeout(140000); @@ -145,35 +360,37 @@ describe("test smart contract interactor", function () { codePath: "src/testdata/lottery-esdt.wasm", gasLimit: 100000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); - let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + let { + bundle: { returnCode }, + } = await controller.deploy(deployTransaction); assert.isTrue(returnCode.isSuccess()); - let startInteraction = contract.methods.start([ - "lucky", - "EGLD", - 1, - null, - null, - 1, - null, - null - ]) - .withGasLimit(30000000) - .withChainID(network.ChainID) - .withSender(alice.address); - - let lotteryStatusInteraction = contract.methods.status(["lucky"]) - .withGasLimit(5000000) - .withChainID(network.ChainID) - .withSender(alice.address); - - let getLotteryInfoInteraction = contract.methods.getLotteryInfo(["lucky"]) - .withGasLimit(5000000) - .withChainID(network.ChainID) - .withSender(alice.address); + let startInteraction = ( + contract.methods + .start(["lucky", "EGLD", 1, null, null, 1, null, null]) + .withGasLimit(30000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); + + let lotteryStatusInteraction = ( + contract.methods + .status(["lucky"]) + .withGasLimit(5000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); + + let getLotteryInfoInteraction = ( + contract.methods + .getLotteryInfo(["lucky"]) + .withGasLimit(5000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); // start() let startTransaction = startInteraction @@ -219,11 +436,132 @@ describe("test smart contract interactor", function () { tickets_left: new BigNumber(800), max_entries_per_user: new BigNumber(1), prize_distribution: Buffer.from([0x64]), - prize_pool: new BigNumber("0") + prize_pool: new BigNumber("0"), + }); + }); + + it("should interact with 'lottery-esdt' (local testnet) using the SmartContractTransactionsFactory", async function () { + this.timeout(140000); + + let abiRegistry = await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"); + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + + const bytecode = await promises.readFile("src/testdata/lottery-esdt.wasm"); + + // Deploy the contract + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 100000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionCompletionAwaiter = new TransactionWatcher({ + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }); + + const deployTxHash = await provider.sendTransaction(deployTransaction); + let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); + const untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(untypedBundle.returnCode.isSuccess()); + + // start() + let startTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "start", + arguments: ["lucky", "EGLD", 1, null, null, 1, null, null], + gasLimit: 30000000n, + }); + startTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + startTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(startTransaction)), + ); + + alice.account.incrementNonce(); + + const startTxHash = await provider.sendTransaction(startTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(startTxHash); + let typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("start")); + assert.equal(typedBundle.returnCode.valueOf(), "ok"); + assert.lengthOf(typedBundle.values, 0); + + // status() + let lotteryStatusTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "status", + arguments: ["lucky"], + gasLimit: 5000000n, + }); + lotteryStatusTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + lotteryStatusTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(lotteryStatusTransaction)), + ); + + alice.account.incrementNonce(); + + const statusTxHash = await provider.sendTransaction(lotteryStatusTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(statusTxHash); + typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("status")); + assert.equal(typedBundle.returnCode.valueOf(), "ok"); + assert.lengthOf(typedBundle.values, 1); + assert.equal(typedBundle.firstValue!.valueOf().name, "Running"); + + // getlotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) + let lotteryInfoTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "getLotteryInfo", + arguments: ["lucky"], + gasLimit: 5000000n, + }); + lotteryInfoTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + lotteryInfoTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(lotteryInfoTransaction)), + ); + + alice.account.incrementNonce(); + + const infoTxHash = await provider.sendTransaction(lotteryInfoTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(infoTxHash); + typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("getLotteryInfo")); + assert.equal(typedBundle.returnCode.valueOf(), "ok"); + assert.lengthOf(typedBundle.values, 1); + + // Ignore "deadline" field in our test + let info = typedBundle.firstValue!.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: TestWallet }) { + async function signTransaction(options: { transaction: Transaction; wallet: TestWallet }) { const transaction = options.transaction; const wallet = options.wallet; diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 30f3521c8..75040dc0f 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -14,6 +14,8 @@ import { TransactionsFactoryConfig } from "../transactionsFactories/transactions import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; import { promises } from "fs"; import { TransactionComputer } from "../transactionComputer"; +import { QueryRunnerAdapter } from "../adapters/queryRunnerAdapter"; +import { SmartContractQueriesController } from "../smartContractQueriesController"; describe("test on local testnet", function () { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -88,16 +90,105 @@ describe("test on local testnet", function () { simulateTwo.applySignature(await alice.signer.sign(simulateTwo.serializeForSigning())); // Broadcast & execute - await provider.sendTransaction(transactionDeploy); - await provider.sendTransaction(transactionIncrement); + const txHashDeploy = await provider.sendTransaction(transactionDeploy); + const txHashIncrement = await provider.sendTransaction(transactionIncrement); - await watcher.awaitCompleted(transactionDeploy.getHash().hex()); - let transactionOnNetwork = await provider.getTransaction(transactionDeploy.getHash().hex()); + await watcher.awaitCompleted(txHashDeploy); + let transactionOnNetwork = await provider.getTransaction(txHashDeploy); let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); assert.isTrue(bundle.returnCode.isSuccess()); - await watcher.awaitCompleted(transactionIncrement.getHash().hex()); - transactionOnNetwork = await provider.getTransaction(transactionIncrement.getHash().hex()); + await watcher.awaitCompleted(txHashIncrement); + transactionOnNetwork = await provider.getTransaction(txHashIncrement); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + // Simulate + Logger.trace(JSON.stringify(await provider.simulateTransaction(simulateOne), null, 4)); + Logger.trace(JSON.stringify(await provider.simulateTransaction(simulateTwo), null, 4)); + }); + + it("counter: should deploy, then simulate transactions using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const smartContractCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + smartContractCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + smartContractCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(smartContractCallTransaction)), + ); + + alice.account.incrementNonce(); + + const simulateOne = factory.createTransactionForExecute({ + sender: alice.address, + function: "increment", + contract: contractAddress, + gasLimit: 100000n, + }); + + simulateOne.nonce = BigInt(alice.account.nonce.valueOf()); + simulateOne.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(simulateOne)), + ); + + alice.account.incrementNonce(); + + const simulateTwo = factory.createTransactionForExecute({ + sender: alice.address, + function: "foobar", + contract: contractAddress, + gasLimit: 500000n, + }); + + simulateTwo.nonce = BigInt(alice.account.nonce.valueOf()); + simulateTwo.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(simulateTwo)), + ); + + alice.account.incrementNonce(); + + // 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 bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + await watcher.awaitCompleted(callTxHash); + transactionOnNetwork = await provider.getTransaction(callTxHash); bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); assert.isTrue(bundle.returnCode.isSuccess()); @@ -170,6 +261,84 @@ describe("test on local testnet", function () { assert.equal(3, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); }); + it("counter: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { + this.timeout(80000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const firstScCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + firstScCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + firstScCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(firstScCallTransaction)), + ); + + alice.account.incrementNonce(); + + const secondScCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + secondScCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + secondScCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(secondScCallTransaction)), + ); + + alice.account.incrementNonce(); + + // 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 queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const smartContractQueriesController = new SmartContractQueriesController({ queryRunner: queryRunner }); + + const query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "get", + arguments: [], + }); + + const queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(3, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + }); + it("erc20: should deploy, call and query contract", async function () { this.timeout(60000); @@ -256,6 +425,109 @@ describe("test on local testnet", function () { assert.equal(1500, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); }); + it("erc20: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/erc20.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 50000000n, + arguments: [new U32Value(10000)], + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionMintBob = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "transferToken", + gasLimit: 9000000n, + arguments: [new AddressValue(bob.address), new U32Value(1000)], + }); + transactionMintBob.nonce = BigInt(alice.account.nonce.valueOf()); + transactionMintBob.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transactionMintBob)), + ); + + alice.account.incrementNonce(); + + const transactionMintCarol = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "transferToken", + gasLimit: 9000000n, + arguments: [new AddressValue(carol.address), new U32Value(1500)], + }); + transactionMintCarol.nonce = BigInt(alice.account.nonce.valueOf()); + transactionMintCarol.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transactionMintCarol)), + ); + + alice.account.incrementNonce(); + + // 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 queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const smartContractQueriesController = new SmartContractQueriesController({ queryRunner: queryRunner }); + + let query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "totalSupply", + arguments: [], + }); + let queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(10000, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "balanceOf", + arguments: [new AddressValue(alice.address)], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(7500, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "balanceOf", + arguments: [new AddressValue(bob.address)], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(1000, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "balanceOf", + arguments: [new AddressValue(carol.address)], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(1500, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + }); + it("lottery: should deploy, call and query contract", async function () { this.timeout(60000); @@ -332,50 +604,93 @@ describe("test on local testnet", function () { assert.equal(decodeUnsignedNumber(queryResponse.getReturnDataParts()[0]), 0); }); - it("counter: should deploy and call using the SmartContractFactory", async function () { - this.timeout(80000); + it("lottery: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { + this.timeout(60000); TransactionWatcher.DefaultPollingInterval = 5000; TransactionWatcher.DefaultTimeout = 50000; - const network = await provider.getNetworkConfig(); + let network = await provider.getNetworkConfig(); await alice.sync(provider); - const transactionComputer = new TransactionComputer(); const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); const factory = new SmartContractTransactionsFactory({ config: config }); - let bytecode = await promises.readFile("src/testdata/counter.wasm"); + const bytecode = await promises.readFile("src/testdata/lottery-esdt.wasm"); const deployTransaction = factory.createTransactionForDeploy({ sender: alice.address, bytecode: bytecode, - gasLimit: 3000000n, + gasLimit: 50000000n, }); deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); deployTransaction.signature = await alice.signer.sign( Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), ); - const deployTxHash = await provider.sendTransaction(deployTransaction); - await watcher.awaitCompleted(deployTxHash); - const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); - alice.account.incrementNonce(); - const smartContractCallTransaction = factory.createTransactionForExecute({ + const startTransaction = factory.createTransactionForExecute({ sender: alice.address, contract: contractAddress, - function: "increment", - gasLimit: 2000000n, + 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(), + ], }); - smartContractCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); - smartContractCallTransaction.signature = await alice.signer.sign( - Buffer.from(transactionComputer.computeBytesForSigning(smartContractCallTransaction)), + startTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + startTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(startTransaction)), ); - const scCallTxHash = await provider.sendTransaction(smartContractCallTransaction); - await watcher.awaitCompleted(scCallTxHash); + alice.account.incrementNonce(); + + // 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 bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + transactionOnNetwork = await provider.getTransaction(startTx); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + // Query state, do some assertions + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const smartContractQueriesController = new SmartContractQueriesController({ queryRunner: queryRunner }); + + let query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + 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.bech32(), + function: "status", + arguments: [BytesValue.fromUTF8("missingLottery")], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0])), 0); }); }); diff --git a/src/smartcontracts/smartContractResults.local.net.spec.ts b/src/smartcontracts/smartContractResults.local.net.spec.ts index 885cb12f5..1ad837676 100644 --- a/src/smartcontracts/smartContractResults.local.net.spec.ts +++ b/src/smartcontracts/smartContractResults.local.net.spec.ts @@ -5,6 +5,10 @@ import { TransactionWatcher } from "../transactionWatcher"; import { ContractFunction } from "./function"; import { ResultsParser } from "./resultsParser"; import { SmartContract } from "./smartContract"; +import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig"; +import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; +import { promises } from "fs"; +import { TransactionComputer } from "../transactionComputer"; describe("fetch transactions from local testnet", function () { let alice: TestWallet; @@ -16,7 +20,9 @@ describe("fetch transactions from local testnet", function () { before(async function () { ({ alice } = await loadTestWallets()); watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { return await provider.getTransaction(hash, true) } + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, }); }); @@ -38,7 +44,7 @@ describe("fetch transactions from local testnet", function () { codePath: "src/testdata/counter.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); // ++ @@ -46,7 +52,7 @@ describe("fetch transactions from local testnet", function () { func: new ContractFunction("increment"), gasLimit: 3000000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); transactionIncrement.setNonce(alice.account.nonce); @@ -55,14 +61,73 @@ describe("fetch transactions from local testnet", function () { alice.account.incrementNonce(); // Broadcast & execute - await provider.sendTransaction(transactionDeploy); - await provider.sendTransaction(transactionIncrement); + 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 bundle = resultsParser.parseUntypedOutcome(transactionOnNetworkDeploy); + assert.isTrue(bundle.returnCode.isSuccess()); + + bundle = resultsParser.parseUntypedOutcome(transactionOnNetworkIncrement); + assert.isTrue(bundle.returnCode.isSuccess()); + }); + + it("interact with counter smart contract using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const smartContractCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + smartContractCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + smartContractCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(smartContractCallTransaction)), + ); + + alice.account.incrementNonce(); + + // Broadcast & execute + const deployTxHash = await provider.sendTransaction(deployTransaction); + const callTxHash = await provider.sendTransaction(smartContractCallTransaction); - await watcher.awaitCompleted(transactionDeploy.getHash().hex()); - await watcher.awaitCompleted(transactionIncrement.getHash().hex()); + await watcher.awaitCompleted(deployTxHash); + await watcher.awaitCompleted(callTxHash); - let transactionOnNetworkDeploy = await provider.getTransaction(transactionDeploy.getHash().hex()); - let transactionOnNetworkIncrement = await provider.getTransaction(transactionIncrement.getHash().hex()); + let transactionOnNetworkDeploy = await provider.getTransaction(deployTxHash); + let transactionOnNetworkIncrement = await provider.getTransaction(callTxHash); let bundle = resultsParser.parseUntypedOutcome(transactionOnNetworkDeploy); assert.isTrue(bundle.returnCode.isSuccess()); diff --git a/src/testutils/contractController.ts b/src/testutils/contractController.ts index 1d1b7ca19..517d8eef6 100644 --- a/src/testutils/contractController.ts +++ b/src/testutils/contractController.ts @@ -21,26 +21,26 @@ export class ContractController { } async deploy(transaction: Transaction): Promise<{ transactionOnNetwork: ITransactionOnNetwork, bundle: UntypedOutcomeBundle }> { - Logger.info(`ContractController.deploy [begin]: transaction = ${transaction.getHash()}`); - - await this.provider.sendTransaction(transaction); - let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(transaction.getHash().hex()); + const txHash = await this.provider.sendTransaction(transaction); + Logger.info(`ContractController.deploy [begin]: transaction = ${txHash}`); + + let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(txHash); let bundle = this.parser.parseUntypedOutcome(transactionOnNetwork); - Logger.info(`ContractController.deploy [end]: transaction = ${transaction.getHash()}, return code = ${bundle.returnCode}`); + Logger.info(`ContractController.deploy [end]: transaction = ${txHash}, return code = ${bundle.returnCode}`); return { transactionOnNetwork, bundle }; } async execute(interaction: Interaction, transaction: Transaction): Promise<{ transactionOnNetwork: ITransactionOnNetwork, bundle: TypedOutcomeBundle }> { - Logger.info(`ContractController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${transaction.getHash()}`); + const txHash = await this.provider.sendTransaction(transaction); + Logger.info(`ContractController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${txHash}`); interaction.check(); - await this.provider.sendTransaction(transaction); - let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(transaction.getHash().hex()); + let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(txHash); let bundle = this.parser.parseOutcome(transactionOnNetwork, interaction.getEndpoint()); - Logger.info(`ContractController.execute [end]: function = ${interaction.getFunction()}, transaction = ${transaction.getHash()}, return code = ${bundle.returnCode}`); + Logger.info(`ContractController.execute [end]: function = ${interaction.getFunction()}, transaction = ${txHash}, return code = ${bundle.returnCode}`); return { transactionOnNetwork, bundle }; } diff --git a/src/transaction.local.net.spec.ts b/src/transaction.local.net.spec.ts index 97887ecf6..844091230 100644 --- a/src/transaction.local.net.spec.ts +++ b/src/transaction.local.net.spec.ts @@ -2,7 +2,7 @@ import BigNumber from "bignumber.js"; import { assert } from "chai"; import { Logger } from "./logger"; import { loadTestWallets, TestWallet } from "./testutils"; -import { createLocalnetProvider } from "./testutils/networkProviders"; +import { createLocalnetProvider, INetworkProvider } from "./testutils/networkProviders"; import { TokenTransfer } from "./tokens"; import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; @@ -18,22 +18,24 @@ describe("test transaction", function () { ({ alice, bob } = await loadTestWallets()); }); + function createTransactionWatcher(provider: INetworkProvider) { + return new TransactionWatcher( + { + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }, + { timeoutMilliseconds: 100000 }, + ); + } + it("should send transactions and wait for completion", async function () { this.timeout(70000); let provider = createLocalnetProvider(); - let watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash, true); - }, - }); + let watcher = createTransactionWatcher(provider); let network = await provider.getNetworkConfig(); - await alice.sync(provider); - - await bob.sync(provider); - let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); - let transactionOne = new Transaction({ sender: alice.address, receiver: bob.address, @@ -50,6 +52,10 @@ describe("test transaction", function () { chainID: network.ChainID, }); + await alice.sync(provider); + await bob.sync(provider); + let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); + transactionOne.setNonce(alice.account.nonce); alice.account.incrementNonce(); transactionTwo.setNonce(alice.account.nonce); @@ -73,18 +79,10 @@ describe("test transaction", function () { this.timeout(70000); let provider = createLocalnetProvider(); - let watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash, true); - }, - }); + let watcher = createTransactionWatcher(provider); let network = await provider.getNetworkConfig(); - await alice.sync(provider); - await bob.sync(provider); - let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); - let transactionOne = new Transaction({ sender: alice.address, receiver: bob.address, @@ -93,6 +91,10 @@ describe("test transaction", function () { chainID: network.ChainID, }); + await alice.sync(provider); + await bob.sync(provider); + let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); + transactionOne.setNonce(alice.account.nonce); await signTransaction({ transaction: transactionOne, wallet: alice }); await provider.sendTransaction(transactionOne); @@ -143,11 +145,7 @@ describe("test transaction", function () { this.timeout(70000); const provider = createLocalnetProvider(); - const watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { - return await provider.getTransaction(hash, true); - }, - }); + const watcher = createTransactionWatcher(provider); const network = await provider.getNetworkConfig(); diff --git a/src/transactionWatcher.ts b/src/transactionWatcher.ts index 7acd56a3f..ae077b38b 100644 --- a/src/transactionWatcher.ts +++ b/src/transactionWatcher.ts @@ -154,7 +154,9 @@ export class TransactionWatcher { typeof transactionOrTxHash === "string" ? transactionOrTxHash : transactionOrTxHash.getHash().hex(); if (hash.length !== HEX_TRANSACTION_HASH_LENGTH) { - throw new Err("Invalid transaction hash length. The length of a hex encoded hash should be 64."); + throw new Err( + `Invalid transaction hash length. The length of a hex encoded hash should be ${HEX_TRANSACTION_HASH_LENGTH}.`, + ); } return hash;