From b9fa4b6315d2b6042ca3cd3520ca35e1b1e88d49 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 18 Mar 2022 16:08:38 +0200 Subject: [PATCH 01/37] Define interaction.check(). --- src/smartcontracts/interaction.spec.ts | 15 ++++++++++++--- src/smartcontracts/interaction.ts | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index dfb2620a..3d9c7dee 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -119,7 +119,11 @@ describe("test smart contract interactor", function() { let abi = new SmartContractAbi(abiRegistry, ["answer"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let interaction = contract.methods.getUltimateAnswer().withGasLimit(new GasLimit(543210)); + let interaction = contract.methods + .getUltimateAnswer() + .withGasLimit(new GasLimit(543210)) + .check(); + assert.equal(interaction.getContract().getAddress(), dummyAddress); assert.deepEqual(interaction.getInterpretingFunction(), new ContractFunction("getUltimateAnswer")); assert.deepEqual(interaction.getExecutingFunction(), new ContractFunction("getUltimateAnswer")); @@ -181,8 +185,12 @@ describe("test smart contract interactor", function() { let contract = new SmartContract({ address: dummyAddress, abi: abi }); let getInteraction = contract.methods.get(); - let incrementInteraction = (contract.methods.increment()).withGasLimit(new GasLimit(543210)); - let decrementInteraction = (contract.methods.decrement()).withGasLimit(new GasLimit(987654)); + let incrementInteraction = (contract.methods.increment()) + .withGasLimit(new GasLimit(543210)) + .check(); + let decrementInteraction = (contract.methods.decrement()) + .withGasLimit(new GasLimit(987654)) + .check(); // For "get()", return fake 7 provider.mockQueryResponseOnFunction( @@ -242,6 +250,7 @@ describe("test smart contract interactor", function() { OptionValue.newMissing(), ]) .withGasLimit(new GasLimit(5000000)) + .check() ); let lotteryStatusInteraction = ( diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index be9d99fd..d19e6dd0 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -12,6 +12,7 @@ import { Nonce } from "../nonce"; import { ExecutionResultsBundle, QueryResponseBundle } from "./interface"; import { NetworkConfig } from "../networkConfig"; import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; +import { IInteractionChecker, StrictChecker } from "."; /** * Interactions can be seen as mutable transaction & query builders. @@ -20,6 +21,7 @@ import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDT * the execution outcome for the objects they've built. */ export class Interaction { + private readonly checker: IInteractionChecker; private readonly contract: SmartContract; private readonly executingFunction: ContractFunction; private readonly interpretingFunction: ContractFunction; @@ -43,6 +45,7 @@ export class Interaction { args: TypedValue[], receiver?: Address, ) { + this.checker = new StrictChecker(); this.contract = contract; this.executingFunction = executingFunction; this.interpretingFunction = interpretingFunction; @@ -201,6 +204,18 @@ export class Interaction { return this; } + /** + * Checks the prepared interaction against the ABI endpoint definition. + * This function throws if type mismatches (provided vs. ABI) are encountered. + * + * When the ABI is available, it is always recommended to call {@link check} before + * {@link buildTransaction}. + */ + check(): Interaction { + this.checker.checkInteraction(this); + return this; + } + getEndpoint(): EndpointDefinition { return this.getContract().getAbi().getEndpoint(this.getInterpretingFunction()); } From 4bdd259d2483753285b584492e4c2320f53ba697 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 18 Mar 2022 19:08:07 +0200 Subject: [PATCH 02/37] Reimplement default interaction runner (do not sign anymore). --- src/smartcontracts/defaultRunner.ts | 65 +++++-------------- .../interaction.local.net.spec.ts | 38 +++++++---- src/smartcontracts/interaction.spec.ts | 61 +++++++++++++---- src/smartcontracts/interaction.ts | 21 ++++-- src/smartcontracts/interface.ts | 8 --- 5 files changed, 109 insertions(+), 84 deletions(-) diff --git a/src/smartcontracts/defaultRunner.ts b/src/smartcontracts/defaultRunner.ts index 4979638e..220f7609 100644 --- a/src/smartcontracts/defaultRunner.ts +++ b/src/smartcontracts/defaultRunner.ts @@ -1,69 +1,40 @@ import { IProvider, ISigner } from "../interface"; -import { ExecutionResultsBundle, IInteractionChecker, IInteractionRunner, QueryResponseBundle } from "./interface"; +import { ExecutionResultsBundle, QueryResponseBundle } from "./interface"; import { Interaction } from "./interaction"; import { Transaction } from "../transaction"; -import { Address } from "../address"; /** - * An interaction runner suitable for backends or wallets. - * Not suitable for dapps, which depend on external signers (wallets, ledger etc.). + * An interaction runner, suitable for frontends and dApp, + * where signing is performed by means of an external wallet provider. */ -export class DefaultInteractionRunner implements IInteractionRunner { - private readonly checker: IInteractionChecker; - private readonly signer: ISigner; +export class DefaultInteractionRunner { private readonly provider: IProvider; - constructor(checker: IInteractionChecker, signer: ISigner, provider: IProvider) { - this.checker = checker; - this.signer = signer; + constructor(provider: IProvider) { this.provider = provider; } /** - * Given an interaction, broadcasts its compiled transaction. + * Broadcasts an alredy-signed interaction transaction, and also waits for its execution on the Network. + * + * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand + * @param sourceInteraction The interaction used to build the {@link signedInteractionTransaction} */ - async run(interaction: Interaction): Promise { - this.checkInteraction(interaction); - - let transaction = interaction.buildTransaction(); - await this.signer.sign(transaction); - await transaction.send(this.provider); - return transaction; - } - - /** - * Given an interaction, broadcasts its compiled transaction (and also waits for its execution on the Network). - */ - async runAwaitExecution(interaction: Interaction): Promise { - this.checkInteraction(interaction); - - let transaction = await this.run(interaction); - await transaction.awaitExecuted(this.provider); - // This will wait until the transaction is notarized, as well (so that SCRs are returned by the API). - let transactionOnNetwork = await transaction.getAsOnNetwork(this.provider); - let bundle = interaction.interpretExecutionResults(transactionOnNetwork); + async run(signedInteractionTransaction: Transaction, sourceInteraction: Interaction): Promise { + await signedInteractionTransaction.send(this.provider); + await signedInteractionTransaction.awaitExecuted(this.provider); + + let transactionOnNetwork = await signedInteractionTransaction.getAsOnNetwork(this.provider); + // TODO: do not rely on interpretExecutionResults, as it may throw unexpectedly. + let bundle = sourceInteraction.interpretExecutionResults(transactionOnNetwork); return bundle; } - async runQuery(interaction: Interaction, caller?: Address): Promise { - this.checkInteraction(interaction); - + async runQuery(interaction: Interaction): Promise { let query = interaction.buildQuery(); - query.caller = caller || this.signer.getAddress(); let response = await this.provider.queryContract(query); + // TODO: do not rely on interpretQueryResponse, as it may throw unexpectedly. let bundle = interaction.interpretQueryResponse(response); return bundle; } - - async runSimulation(interaction: Interaction): Promise { - this.checkInteraction(interaction); - - let transaction = interaction.buildTransaction(); - await this.signer.sign(transaction); - return await transaction.simulate(this.provider); - } - - private checkInteraction(interaction: Interaction) { - this.checker.checkInteraction(interaction); - } } diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 1b3ed40d..11807b7f 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -16,13 +16,12 @@ import { chooseProxyProvider } from "../interactive"; describe("test smart contract interactor", function () { - let checker = new StrictChecker(); let provider = chooseProxyProvider("local-testnet"); let alice: TestWallet; let runner: DefaultInteractionRunner; before(async function () { ({ alice } = await loadTestWallets()); - runner = new DefaultInteractionRunner(checker, alice.signer, provider); + runner = new DefaultInteractionRunner(provider); }); it("should interact with 'answer' (local testnet)", async function () { @@ -47,9 +46,13 @@ describe("test smart contract interactor", function () { assert.isTrue(queryResponseBundle.returnCode.equals(ReturnCode.Ok)); // Execute, do not wait for execution - await runner.run(interaction.withNonce(alice.account.getNonceThenIncrement())); + let transaction = interaction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(transaction); + await transaction.send(provider); // Execute, and wait for execution - let executionResultsBundle = await runner.runAwaitExecution(interaction.withNonce(alice.account.getNonceThenIncrement())); + transaction = interaction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(transaction); + let executionResultsBundle = await runner.run(transaction, interaction); assert.lengthOf(executionResultsBundle.values, 1); assert.deepEqual(executionResultsBundle.firstValue.valueOf(), new BigNumber(42)); @@ -78,12 +81,19 @@ describe("test smart contract interactor", function () { assert.deepEqual(counterValue.valueOf(), new BigNumber(1)); // Increment, wait for execution. - let { firstValue: valueAfterIncrement } = await runner.runAwaitExecution(incrementInteraction.withNonce(alice.account.getNonceThenIncrement())); + let incrementTransaction = incrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(incrementTransaction); + let { firstValue: valueAfterIncrement } = await runner.run(incrementTransaction, incrementInteraction); assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(2)); - // Decrement. Wait for execution of the second transaction. - await runner.run(decrementInteraction.withNonce(alice.account.getNonceThenIncrement())); - let { firstValue: valueAfterDecrement } = await runner.runAwaitExecution(decrementInteraction.withNonce(alice.account.getNonceThenIncrement())) + // Decrement twice. Wait for execution of the second transaction. + let decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(decrementTransaction); + await decrementTransaction.send(provider); + + decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(decrementTransaction); + let { firstValue: valueAfterDecrement } = await runner.run(decrementTransaction, decrementInteraction); assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(0)); }); @@ -119,18 +129,24 @@ describe("test smart contract interactor", function () { ]).withGasLimit(new GasLimit(15000000)); // start() - let { returnCode: startReturnCode, values: startReturnvalues } = await runner.runAwaitExecution(startInteraction.withNonce(alice.account.getNonceThenIncrement())) + let startTransaction = startInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(startTransaction); + let { returnCode: startReturnCode, values: startReturnvalues } = await runner.run(startTransaction, startInteraction); assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); assert.lengthOf(startReturnvalues, 0); // status() - let { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } = await runner.runAwaitExecution(lotteryStatusInteraction.withNonce(alice.account.getNonceThenIncrement())) + let statusTransaction = lotteryStatusInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(statusTransaction); + let { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } = await runner.run(statusTransaction, lotteryStatusInteraction); assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); assert.lengthOf(statusReturnValues, 1); assert.equal(statusFirstValue.valueOf().name, "Running"); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue } = await runner.runAwaitExecution(getLotteryInfoInteraction.withNonce(alice.account.getNonceThenIncrement())) + let lotteryInfoTransaction = getLotteryInfoInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(lotteryInfoTransaction); + let { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue } = await runner.run(lotteryInfoTransaction, getLotteryInfoInteraction); assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); assert.lengthOf(infoReturnValues, 1); diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 3d9c7dee..8158b827 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -1,4 +1,3 @@ -import { StrictChecker } from "./strictChecker"; import { DefaultInteractionRunner } from "./defaultRunner"; import { SmartContract } from "./smartContract"; import { BigUIntValue, OptionValue, U32Value } from "./typesystem"; @@ -29,13 +28,13 @@ import { createBalanceBuilder } from "../balanceBuilder"; describe("test smart contract interactor", function() { let dummyAddress = new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); - let checker = new StrictChecker(); let provider = new MockProvider(); let alice: TestWallet; let runner: DefaultInteractionRunner; + before(async function() { ({ alice } = await loadTestWallets()); - runner = new DefaultInteractionRunner(checker, alice.signer, provider); + runner = new DefaultInteractionRunner(provider); }); it("should set transaction fields", async function () { @@ -144,7 +143,9 @@ describe("test smart contract interactor", function() { assert.isTrue(queryCode.equals(ReturnCode.Ok)); // Execute, do not wait for execution - let transaction = await runner.run(interaction.withNonce(new Nonce(0))); + let transaction = interaction.withNonce(new Nonce(0)).buildTransaction(); + await alice.signer.sign(transaction); + transaction.send(provider); assert.equal(transaction.getNonce().valueOf(), 0); assert.equal(transaction.getData().toString(), "getUltimateAnswer"); assert.equal( @@ -152,7 +153,9 @@ describe("test smart contract interactor", function() { "60d0956a8902c1179dce92d91bd9670e31b9a9cd07c1d620edb7754a315b4818" ); - transaction = await runner.run(interaction.withNonce(new Nonce(1))); + transaction = interaction.withNonce(new Nonce(1)).buildTransaction(); + await alice.signer.sign(transaction); + await transaction.send(provider); assert.equal(transaction.getNonce().valueOf(), 1); assert.equal( transaction.getHash().toString(), @@ -169,7 +172,11 @@ describe("test smart contract interactor", function() { new AddImmediateResult("@6f6b@2b"), new MarkNotarized(), ]), - runner.runAwaitExecution(interaction.withNonce(new Nonce(2))), + (async () => { + let transaction = interaction.withNonce(new Nonce(2)).buildTransaction(); + await alice.signer.sign(transaction); + return await runner.run(transaction, interaction); + })() ]); assert.lengthOf(executionValues, 1); @@ -210,22 +217,36 @@ describe("test smart contract interactor", function() { new AddImmediateResult("@6f6b@08"), new MarkNotarized(), ]), - runner.runAwaitExecution(incrementInteraction.withNonce(new Nonce(14))), + (async () => { + let transaction = incrementInteraction.withNonce(new Nonce(14)).buildTransaction(); + await alice.signer.sign(transaction); + return await runner.run(transaction, incrementInteraction); + })() ]); assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(8)); // Decrement three times (simulate three parallel broadcasts). Wait for execution of the latter (third transaction). Return fake "5". - await runner.run(decrementInteraction.withNonce(new Nonce(15))); - await runner.run(decrementInteraction.withNonce(new Nonce(16))); - + // Decrement #1 + let decrementTransaction = decrementInteraction.withNonce(new Nonce(15)).buildTransaction(); + await alice.signer.sign(decrementTransaction); + decrementTransaction.send(provider); + // Decrement #2 + decrementTransaction = decrementInteraction.withNonce(new Nonce(16)).buildTransaction(); + await alice.signer.sign(decrementTransaction); + decrementTransaction.send(provider); + // Decrement #3 let [, { firstValue: valueAfterDecrement }] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), new AddImmediateResult("@6f6b@05"), new MarkNotarized(), ]), - runner.runAwaitExecution(decrementInteraction.withNonce(new Nonce(17))), + (async () => { + let decrementTransaction = decrementInteraction.withNonce(new Nonce(17)).buildTransaction(); + await alice.signer.sign(decrementTransaction); + return await runner.run(decrementTransaction, decrementInteraction); + })() ]); assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(5)); @@ -268,7 +289,11 @@ describe("test smart contract interactor", function() { new AddImmediateResult("@6f6b"), new MarkNotarized(), ]), - runner.runAwaitExecution(startInteraction.withNonce(new Nonce(14))), + (async () => { + let transaction = startInteraction.withNonce(new Nonce(14)).buildTransaction(); + await alice.signer.sign(transaction); + return await runner.run(transaction, startInteraction); + })() ]); assert.equal( @@ -291,7 +316,11 @@ describe("test smart contract interactor", function() { new AddImmediateResult("@6f6b@01"), new MarkNotarized(), ]), - runner.runAwaitExecution(lotteryStatusInteraction.withNonce(new Nonce(15))), + (async () => { + let transaction = startInteraction.withNonce(new Nonce(15)).buildTransaction(); + await alice.signer.sign(transaction); + return await runner.run(transaction, lotteryStatusInteraction); + })() ]); assert.equal( @@ -317,7 +346,11 @@ describe("test smart contract interactor", function() { ), new MarkNotarized(), ]), - runner.runAwaitExecution(getLotteryInfoInteraction.withNonce(new Nonce(16))), + (async () => { + let transaction = getLotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); + await alice.signer.sign(transaction); + return await runner.run(transaction, getLotteryInfoInteraction); + })() ]); assert.equal( diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index d19e6dd0..a6d595d6 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -9,10 +9,11 @@ import { Address } from "../address"; import { SmartContract } from "./smartContract"; import { AddressValue, BigUIntValue, BytesValue, EndpointDefinition, TypedValue, U64Value, U8Value } from "./typesystem"; import { Nonce } from "../nonce"; -import { ExecutionResultsBundle, QueryResponseBundle } from "./interface"; +import { ExecutionResultsBundle, IInteractionChecker, QueryResponseBundle } from "./interface"; import { NetworkConfig } from "../networkConfig"; import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; -import { IInteractionChecker, StrictChecker } from "."; +import { StrictChecker } from "./strictChecker"; +import { Account } from "../account"; /** * Interactions can be seen as mutable transaction & query builders. @@ -31,6 +32,7 @@ export class Interaction { private nonce: Nonce = new Nonce(0); private value: Balance = Balance.Zero(); private gasLimit: GasLimit = GasLimit.min(); + private querent: Address = new Address(); private isWithSingleESDTTransfer: boolean = false; private isWithSingleESDTNFTTransfer: boolean = false; @@ -124,8 +126,7 @@ export class Interaction { args: this.args, // Value will be set using "withValue()". value: this.value, - // Caller will be set by the InteractionRunner. - caller: new Address() + caller: this.querent }); } @@ -204,6 +205,18 @@ export class Interaction { return this; } + useThenIncrementNonceOf(account: Account) : Interaction { + return this.withNonce(account.getNonceThenIncrement()); + } + + /** + * Sets the "caller" field on contract queries. + */ + withQuerent(querent: Address): Interaction { + this.querent = querent; + return this; + } + /** * Checks the prepared interaction against the ABI endpoint definition. * This function throws if type mismatches (provided vs. ABI) are encountered. diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index a0c9ec5c..5103c3ea 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -37,14 +37,6 @@ export interface ISmartContract { call({ func, args, value, gasLimit }: CallArguments): Transaction; } -export interface IInteractionRunner { - run(interaction: Interaction): Promise; - runAwaitExecution(interaction: Interaction): Promise; - runQuery(interaction: Interaction, caller?: Address): Promise; - // TODO: Fix method signature (underspecified at this moment). - runSimulation(interaction: Interaction): Promise; -} - export interface IInteractionChecker { checkInteraction(interaction: Interaction): void; } From 54d3882cadd551569f9e9645271aae235967000c Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sat, 19 Mar 2022 00:29:28 +0200 Subject: [PATCH 03/37] Simplify SmartContractResults. Breaking change. --- src/smartcontracts/defaultRunner.ts | 14 +- src/smartcontracts/interaction.spec.ts | 111 +++++++-------- src/smartcontracts/interaction.ts | 57 ++------ src/smartcontracts/interface.ts | 12 -- src/smartcontracts/queryResponse.ts | 24 +++- src/smartcontracts/smartContract.ts | 2 +- .../smartContractResults.local.net.spec.ts | 18 +-- src/smartcontracts/smartContractResults.ts | 101 +------------ src/smartcontracts/wrapper/contractLogger.ts | 11 +- .../wrapper/contractWrapper.spec.ts | 5 +- src/smartcontracts/wrapper/contractWrapper.ts | 12 +- src/smartcontracts/wrapper/deprecatedSCRs.ts | 134 ++++++++++++++++++ src/smartcontracts/{ => wrapper}/result.ts | 11 +- src/testutils/mockProvider.ts | 12 -- 14 files changed, 239 insertions(+), 285 deletions(-) create mode 100644 src/smartcontracts/wrapper/deprecatedSCRs.ts rename src/smartcontracts/{ => wrapper}/result.ts (83%) diff --git a/src/smartcontracts/defaultRunner.ts b/src/smartcontracts/defaultRunner.ts index 220f7609..b3645bdc 100644 --- a/src/smartcontracts/defaultRunner.ts +++ b/src/smartcontracts/defaultRunner.ts @@ -1,7 +1,8 @@ -import { IProvider, ISigner } from "../interface"; -import { ExecutionResultsBundle, QueryResponseBundle } from "./interface"; +import { IProvider } from "../interface"; +import { QueryResponseBundle } from "./interface"; import { Interaction } from "./interaction"; import { Transaction } from "../transaction"; +import { TransactionOnNetwork } from "../transactionOnNetwork"; /** * An interaction runner, suitable for frontends and dApp, @@ -18,16 +19,11 @@ export class DefaultInteractionRunner { * Broadcasts an alredy-signed interaction transaction, and also waits for its execution on the Network. * * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand - * @param sourceInteraction The interaction used to build the {@link signedInteractionTransaction} */ - async run(signedInteractionTransaction: Transaction, sourceInteraction: Interaction): Promise { + async run(signedInteractionTransaction: Transaction): Promise { await signedInteractionTransaction.send(this.provider); await signedInteractionTransaction.awaitExecuted(this.provider); - - let transactionOnNetwork = await signedInteractionTransaction.getAsOnNetwork(this.provider); - // TODO: do not rely on interpretExecutionResults, as it may throw unexpectedly. - let bundle = sourceInteraction.interpretExecutionResults(transactionOnNetwork); - return bundle; + return await signedInteractionTransaction.getAsOnNetwork(this.provider); } async runQuery(interaction: Interaction): Promise { diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 8158b827..c6317c96 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -2,7 +2,6 @@ import { DefaultInteractionRunner } from "./defaultRunner"; import { SmartContract } from "./smartContract"; import { BigUIntValue, OptionValue, U32Value } from "./typesystem"; import { - AddImmediateResult, loadAbiRegistry, loadTestWallets, MarkNotarized, @@ -40,7 +39,7 @@ describe("test smart contract interactor", function() { it("should set transaction fields", async function () { let contract = new SmartContract({ address: dummyAddress }); let dummyFunction = new ContractFunction("dummy"); - let interaction = new Interaction(contract, dummyFunction, dummyFunction, []); + let interaction = new Interaction(contract, dummyFunction, []); let transaction = interaction .withNonce(new Nonce(7)) @@ -76,35 +75,35 @@ describe("test smart contract interactor", function() { const hexDummyFunction = "64756d6d79"; // ESDT, single - let transaction = new Interaction(contract, dummyFunction, dummyFunction, []) + let transaction = new Interaction(contract, dummyFunction, []) .withSingleESDTTransfer(TokenFoo("10")) .buildTransaction(); assert.equal(transaction.getData().toString(), `ESDTTransfer@${hexFoo}@0a@${hexDummyFunction}`); // Meta ESDT (special SFT), single - transaction = new Interaction(contract, dummyFunction, dummyFunction, []) + transaction = new Interaction(contract, dummyFunction, []) .withSingleESDTNFTTransfer(LKMEX.nonce(123456).value(123.456), alice) .buildTransaction(); assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`); // NFT, single - transaction = new Interaction(contract, dummyFunction, dummyFunction, []) + transaction = new Interaction(contract, dummyFunction, []) .withSingleESDTNFTTransfer(Strămoși.nonce(1).one(), alice) .buildTransaction(); assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexStrămoși}@01@01@${hexContractAddress}@${hexDummyFunction}`); // ESDT, multiple - transaction = new Interaction(contract, dummyFunction, dummyFunction, []) + transaction = new Interaction(contract, dummyFunction, []) .withMultiESDTNFTTransfer([TokenFoo(3), TokenBar(3.14)], alice) .buildTransaction(); assert.equal(transaction.getData().toString(), `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`); // NFT, multiple - transaction = new Interaction(contract, dummyFunction, dummyFunction, []) + transaction = new Interaction(contract, dummyFunction, []) .withMultiESDTNFTTransfer([Strămoși.nonce(1).one(), Strămoși.nonce(42).one()], alice) .buildTransaction(); @@ -124,8 +123,7 @@ describe("test smart contract interactor", function() { .check(); assert.equal(interaction.getContract().getAddress(), dummyAddress); - assert.deepEqual(interaction.getInterpretingFunction(), new ContractFunction("getUltimateAnswer")); - assert.deepEqual(interaction.getExecutingFunction(), new ContractFunction("getUltimateAnswer")); + assert.deepEqual(interaction.getFunction(), new ContractFunction("getUltimateAnswer")); assert.lengthOf(interaction.getArguments(), 0); assert.deepEqual(interaction.getGasLimit(), new GasLimit(543210)); @@ -163,25 +161,22 @@ describe("test smart contract interactor", function() { ); // Execute, and wait for execution - let [ - , - { values: executionValues, firstValue: executionAnswer, returnCode: executionCode }, - ] = await Promise.all([ + let [, transactionOnNetwork ] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult("@6f6b@2b"), + // TODO: @6f6b@2bs new MarkNotarized(), ]), (async () => { let transaction = interaction.withNonce(new Nonce(2)).buildTransaction(); await alice.signer.sign(transaction); - return await runner.run(transaction, interaction); + return await runner.run(transaction); })() ]); - assert.lengthOf(executionValues, 1); - assert.deepEqual(executionAnswer.valueOf(), new BigNumber(43)); - assert.isTrue(executionCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(executionValues, 1); + // TODO: assert.deepEqual(executionAnswer.valueOf(), new BigNumber(43)); + // TODO: assert.isTrue(executionCode.equals(ReturnCode.Ok)); }); it("should interact with 'counter'", async function() { @@ -211,20 +206,20 @@ describe("test smart contract interactor", function() { assert.deepEqual(counterValue.valueOf(), new BigNumber(7)); // Increment, wait for execution. Return fake 8 - let [, { firstValue: valueAfterIncrement }] = await Promise.all([ + let [, transactionOnNetworkForIncrement ] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult("@6f6b@08"), + // TODO: @6f6b@08 new MarkNotarized(), ]), (async () => { let transaction = incrementInteraction.withNonce(new Nonce(14)).buildTransaction(); await alice.signer.sign(transaction); - return await runner.run(transaction, incrementInteraction); + return await runner.run(transaction); })() ]); - assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(8)); + // TODO: assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(8)); // Decrement three times (simulate three parallel broadcasts). Wait for execution of the latter (third transaction). Return fake "5". // Decrement #1 @@ -236,20 +231,20 @@ describe("test smart contract interactor", function() { await alice.signer.sign(decrementTransaction); decrementTransaction.send(provider); // Decrement #3 - let [, { firstValue: valueAfterDecrement }] = await Promise.all([ + let [, transactionOnNetworkForDecrement ] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult("@6f6b@05"), + // TODO: @6f6b@05 new MarkNotarized(), ]), (async () => { let decrementTransaction = decrementInteraction.withNonce(new Nonce(17)).buildTransaction(); await alice.signer.sign(decrementTransaction); - return await runner.run(decrementTransaction, decrementInteraction); + return await runner.run(decrementTransaction); })() ]); - assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(5)); + // TODO: assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(5)); }); it("should interact with 'lottery_egld'", async function() { @@ -283,16 +278,16 @@ describe("test smart contract interactor", function() { ); // start() - let [, { returnCode: startReturnCode, values: startReturnvalues }] = await Promise.all([ + let [, transactionOnNetworkForStart ] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult("@6f6b"), + // TODO: @6f6b new MarkNotarized(), ]), (async () => { let transaction = startInteraction.withNonce(new Nonce(14)).buildTransaction(); await alice.signer.sign(transaction); - return await runner.run(transaction, startInteraction); + return await runner.run(transaction); })() ]); @@ -303,23 +298,20 @@ describe("test smart contract interactor", function() { .toString(), "start@6c75636b79@0de0b6b3a7640000@@@0100000001@@" ); - assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); - assert.lengthOf(startReturnvalues, 0); + // TODO: assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(startReturnvalues, 0); // lotteryExists() (this is a view function, but for the sake of the test, we'll execute it) - let [ - , - { returnCode: statusReturnCode, values: statusReturnvalues, firstValue: statusFirstValue }, - ] = await Promise.all([ + let [, transactionOnNetworkForLotteryExists ] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult("@6f6b@01"), + // TODO: @6f6b@01 new MarkNotarized(), ]), (async () => { - let transaction = startInteraction.withNonce(new Nonce(15)).buildTransaction(); + let transaction = lotteryStatusInteraction.withNonce(new Nonce(15)).buildTransaction(); await alice.signer.sign(transaction); - return await runner.run(transaction, lotteryStatusInteraction); + return await runner.run(transaction); })() ]); @@ -330,26 +322,21 @@ describe("test smart contract interactor", function() { .toString(), "status@6c75636b79" ); - assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); - assert.lengthOf(statusReturnvalues, 1); - assert.deepEqual(statusFirstValue.valueOf(), { name: "Running", fields: [] }); + // TODO: assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(statusReturnvalues, 1); + // TODO: assert.deepEqual(statusFirstValue.valueOf(), { name: "Running", fields: [] }); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let [ - , - { returnCode: infoReturnCode, values: infoReturnvalues, firstValue: infoFirstValue }, - ] = await Promise.all([ + let [, transactionOnNetworkForGetInfo] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult( - "@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000" - ), + // TODO: "@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000" new MarkNotarized(), ]), (async () => { let transaction = getLotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); await alice.signer.sign(transaction); - return await runner.run(transaction, getLotteryInfoInteraction); + return await runner.run(transaction); })() ]); @@ -360,18 +347,18 @@ describe("test smart contract interactor", function() { .toString(), "lotteryInfo@6c75636b79" ); - assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); - assert.lengthOf(infoReturnvalues, 1); - - assert.deepEqual(infoFirstValue.valueOf(), { - ticket_price: new BigNumber("1000000000000000000"), - tickets_left: new BigNumber(800), - deadline: new BigNumber("1611835398"), - max_entries_per_user: new BigNumber(1), - prize_distribution: Buffer.from([0x64]), - whitelist: [], - current_ticket_number: new BigNumber(0), - prize_pool: new BigNumber("0"), - }); + // TODO: assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(infoReturnvalues, 1); + + // TODO: assert.deepEqual(infoFirstValue.valueOf(), { + // ticket_price: new BigNumber("1000000000000000000"), + // tickets_left: new BigNumber(800), + // deadline: new BigNumber("1611835398"), + // max_entries_per_user: new BigNumber(1), + // prize_distribution: Buffer.from([0x64]), + // whitelist: [], + // current_ticket_number: new BigNumber(0), + // prize_pool: new BigNumber("0"), + // }); }); }); diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index a6d595d6..99ecc097 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -1,7 +1,6 @@ import { Balance } from "../balance"; import { GasLimit } from "../networkParams"; import { Transaction } from "../transaction"; -import { TransactionOnNetwork } from "../transactionOnNetwork"; import { Query } from "./query"; import { QueryResponse } from "./queryResponse"; import { ContractFunction } from "./function"; @@ -9,7 +8,7 @@ import { Address } from "../address"; import { SmartContract } from "./smartContract"; import { AddressValue, BigUIntValue, BytesValue, EndpointDefinition, TypedValue, U64Value, U8Value } from "./typesystem"; import { Nonce } from "../nonce"; -import { ExecutionResultsBundle, IInteractionChecker, QueryResponseBundle } from "./interface"; +import { IInteractionChecker, QueryResponseBundle } from "./interface"; import { NetworkConfig } from "../networkConfig"; import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; import { StrictChecker } from "./strictChecker"; @@ -24,8 +23,7 @@ import { Account } from "../account"; export class Interaction { private readonly checker: IInteractionChecker; private readonly contract: SmartContract; - private readonly executingFunction: ContractFunction; - private readonly interpretingFunction: ContractFunction; + private readonly function: ContractFunction; private readonly args: TypedValue[]; private readonly receiver?: Address; @@ -42,15 +40,13 @@ export class Interaction { constructor( contract: SmartContract, - executingFunction: ContractFunction, - interpretingFunction: ContractFunction, + func: ContractFunction, args: TypedValue[], receiver?: Address, ) { this.checker = new StrictChecker(); this.contract = contract; - this.executingFunction = executingFunction; - this.interpretingFunction = interpretingFunction; + this.function = func; this.args = args; this.receiver = receiver; this.tokenTransfers = new TokenTransfersWithinInteraction([], this); @@ -60,12 +56,8 @@ export class Interaction { return this.contract; } - getInterpretingFunction(): ContractFunction { - return this.interpretingFunction; - } - - getExecutingFunction(): ContractFunction { - return this.executingFunction; + getFunction(): ContractFunction { + return this.function; } getArguments(): TypedValue[] { @@ -86,7 +78,7 @@ export class Interaction { buildTransaction(): Transaction { let receiver = this.receiver; - let func: ContractFunction = this.executingFunction; + let func: ContractFunction = this.function; let args = this.args; if (this.isWithSingleESDTTransfer) { @@ -122,7 +114,7 @@ export class Interaction { buildQuery(): Query { return new Query({ address: this.contract.getAddress(), - func: this.executingFunction, + func: this.function, args: this.args, // Value will be set using "withValue()". value: this.value, @@ -130,14 +122,6 @@ export class Interaction { }); } - /** - * Interprets the results of a previously broadcasted (and fully executed) smart contract transaction. - * The outcome is structured such that it allows quick access to each level of detail. - */ - interpretExecutionResults(transactionOnNetwork: TransactionOnNetwork): ExecutionResultsBundle { - return interpretExecutionResults(this.getEndpoint(), transactionOnNetwork); - } - /** * Interprets the raw outcome of a Smart Contract query. * The outcome is structured such that it allows quick access to each level of detail. @@ -230,31 +214,10 @@ export class Interaction { } getEndpoint(): EndpointDefinition { - return this.getContract().getAbi().getEndpoint(this.getInterpretingFunction()); + return this.getContract().getAbi().getEndpoint(this.getFunction()); } } -function interpretExecutionResults(endpoint: EndpointDefinition, transactionOnNetwork: TransactionOnNetwork): ExecutionResultsBundle { - let smartContractResults = transactionOnNetwork.getSmartContractResults(); - let immediateResult = smartContractResults.getImmediate(); - let resultingCalls = smartContractResults.getResultingCalls(); - - immediateResult.setEndpointDefinition(endpoint); - - let values = immediateResult.outputTyped(); - let returnCode = immediateResult.getReturnCode(); - - return { - transactionOnNetwork: transactionOnNetwork, - smartContractResults: smartContractResults, - immediateResult, - resultingCalls, - values, - firstValue: values[0], - returnCode: returnCode - }; -} - class TokenTransfersWithinInteraction { private readonly transfers: Balance[]; private readonly interaction: Interaction; @@ -336,7 +299,7 @@ class TokenTransfersWithinInteraction { } private getTypedInteractionFunction(): TypedValue { - return BytesValue.fromUTF8(this.interaction.getExecutingFunction().valueOf()) + return BytesValue.fromUTF8(this.interaction.getFunction().valueOf()) } private getInteractionArguments(): TypedValue[] { diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index 5103c3ea..456f4815 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -2,14 +2,12 @@ import { Address } from "../address"; import { Balance } from "../balance"; import { GasLimit } from "../networkParams"; import { Transaction } from "../transaction"; -import { TransactionOnNetwork } from "../transactionOnNetwork"; import { Code } from "./code"; import { CodeMetadata } from "./codeMetadata"; import { ContractFunction } from "./function"; import { Interaction } from "./interaction"; import { QueryResponse } from "./queryResponse"; import { ReturnCode } from "./returnCode"; -import { SmartContractResults, TypedResult } from "./smartContractResults"; import { TypedValue } from "./typesystem"; /** @@ -72,16 +70,6 @@ export interface QueryArguments { caller?: Address } -export interface ExecutionResultsBundle { - transactionOnNetwork: TransactionOnNetwork; - smartContractResults: SmartContractResults; - immediateResult: TypedResult; - resultingCalls: TypedResult[]; - values: TypedValue[]; - firstValue: TypedValue; - returnCode: ReturnCode; -} - export interface QueryResponseBundle { queryResponse: QueryResponse; firstValue: TypedValue; diff --git a/src/smartcontracts/queryResponse.ts b/src/smartcontracts/queryResponse.ts index ad9f0565..64e9ff00 100644 --- a/src/smartcontracts/queryResponse.ts +++ b/src/smartcontracts/queryResponse.ts @@ -3,9 +3,11 @@ import { EndpointDefinition, TypedValue } from "./typesystem"; import { MaxUint64 } from "./query"; import { ReturnCode } from "./returnCode"; import BigNumber from "bignumber.js"; -import { Result } from "./result"; +import { ErrContract } from "../errors"; +import { guardValueIsSet } from "../utils"; +import { ArgSerializer } from "./argSerializer"; -export class QueryResponse implements Result.IResult { +export class QueryResponse { /** * If available, will provide typed output arguments (with typed values). */ @@ -50,12 +52,13 @@ export class QueryResponse implements Result.IResult { getReturnMessage(): string { return this.returnMessage; } - unpackOutput(): any { - return Result.unpackOutput(this); - } assertSuccess() { - Result.assertSuccess(this); + if (this.isSuccess()) { + return; + } + + throw new ErrContract(`${this.getReturnCode()}: ${this.getReturnMessage()}`); } isSuccess(): boolean { @@ -74,7 +77,14 @@ export class QueryResponse implements Result.IResult { } outputTyped(): TypedValue[] { - return Result.outputTyped(this); + this.assertSuccess(); + + let endpointDefinition = this.getEndpointDefinition(); + guardValueIsSet("endpointDefinition", endpointDefinition); + + let buffers = this.outputUntyped(); + let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition!.output); + return values; } /** diff --git a/src/smartcontracts/smartContract.ts b/src/smartcontracts/smartContract.ts index b7d46065..54941470 100644 --- a/src/smartcontracts/smartContract.ts +++ b/src/smartcontracts/smartContract.ts @@ -61,7 +61,7 @@ export class SmartContract implements ISmartContract { // and returns a prepared contract interaction. this.methods[functionName] = function (args?: TypedValue[]) { let func = new ContractFunction(functionName); - let interaction = new Interaction(contract, func, func, args || []); + let interaction = new Interaction(contract, func, args || []); return interaction; }; } diff --git a/src/smartcontracts/smartContractResults.local.net.spec.ts b/src/smartcontracts/smartContractResults.local.net.spec.ts index cd4dba23..37e3bb61 100644 --- a/src/smartcontracts/smartContractResults.local.net.spec.ts +++ b/src/smartcontracts/smartContractResults.local.net.spec.ts @@ -56,23 +56,7 @@ describe("fetch transactions from local testnet", function () { await transactionDeploy.getAsOnNetwork(provider); await transactionIncrement.getAsOnNetwork(provider); - let deployImmediateResult = transactionDeploy.getAsOnNetworkCached().getSmartContractResults().getImmediate(); - let deployResultingCalls = transactionDeploy.getAsOnNetworkCached().getSmartContractResults().getResultingCalls(); - let incrementImmediateResult = transactionIncrement.getAsOnNetworkCached().getSmartContractResults().getImmediate(); - let incrementResultingCalls = transactionIncrement.getAsOnNetworkCached().getSmartContractResults().getResultingCalls(); - - deployImmediateResult.assertSuccess(); - incrementImmediateResult.assertSuccess(); - - assert.lengthOf(deployImmediateResult.outputUntyped(), 0); - // There is some refund - assert.isTrue(deployImmediateResult.value.valueOf().gt(0)); - assert.lengthOf(deployResultingCalls, 0); - - assert.lengthOf(incrementImmediateResult.outputUntyped(), 1); - // There is some refund - assert.isTrue(incrementImmediateResult.value.valueOf().gt(0)); - assert.lengthOf(incrementResultingCalls, 0); + // TODO: Assert success in SCRs. }); it("ESDT", async function () { diff --git a/src/smartcontracts/smartContractResults.ts b/src/smartcontracts/smartContractResults.ts index a91152b9..c2b6bda4 100644 --- a/src/smartcontracts/smartContractResults.ts +++ b/src/smartcontracts/smartContractResults.ts @@ -5,25 +5,12 @@ import { GasLimit, GasPrice } from "../networkParams"; import { Nonce } from "../nonce"; import { TransactionHash } from "../transaction"; import { ArgSerializer } from "./argSerializer"; -import { EndpointDefinition, TypedValue } from "./typesystem"; -import { ReturnCode } from "./returnCode"; -import { Result } from "./result"; export class SmartContractResults { private readonly items: SmartContractResultItem[] = []; - private readonly immediate: TypedResult = new TypedResult(); - private readonly resultingCalls: TypedResult[] = []; constructor(items: SmartContractResultItem[]) { this.items = items; - - if (this.items.length > 0) { - let immediateResult = this.findImmediateResult(); - if (immediateResult) { - this.immediate = immediateResult; - } - this.resultingCalls = this.findResultingCalls(); - } } static empty(): SmartContractResults { @@ -35,37 +22,11 @@ export class SmartContractResults { return new SmartContractResults(items); } - private findImmediateResult(): TypedResult | undefined { - let immediateItem = this.items.filter(item => isImmediateResult(item))[0]; - if (immediateItem) { - return new TypedResult(immediateItem); - } - return undefined; - } - - private findResultingCalls(): TypedResult[] { - let otherItems = this.items.filter(item => !isImmediateResult(item)); - let resultingCalls = otherItems.map(item => new TypedResult(item)); - return resultingCalls; - } - - getImmediate(): TypedResult { - return this.immediate; - } - - getResultingCalls(): TypedResult[] { - return this.resultingCalls; - } - - getAllResults(): TypedResult[] { - return this.items.map(item => new TypedResult(item)); + getAll(): SmartContractResultItem[] { + return this.items; } } -function isImmediateResult(item: SmartContractResultItem): boolean { - return item.nonce.valueOf() != 0; -} - export class SmartContractResultItem { hash: Hash = Hash.empty(); nonce: Nonce = new Nonce(0); @@ -78,7 +39,6 @@ export class SmartContractResultItem { gasLimit: GasLimit = new GasLimit(0); gasPrice: GasPrice = new GasPrice(0); callType: number = 0; - returnMessage: string = ""; static fromHttpResponse(response: { hash: string, @@ -107,7 +67,6 @@ export class SmartContractResultItem { item.gasLimit = new GasLimit(response.gasLimit); item.gasPrice = new GasPrice(response.gasPrice); item.callType = response.callType; - item.returnMessage = response.returnMessage; return item; } @@ -116,59 +75,3 @@ export class SmartContractResultItem { return new ArgSerializer().stringToBuffers(this.data); } } - -export class TypedResult extends SmartContractResultItem implements Result.IResult { - /** - * If available, will provide typed output arguments (with typed values). - */ - endpointDefinition?: EndpointDefinition; - - constructor(init?: Partial) { - super(); - Object.assign(this, init); - } - - assertSuccess() { - Result.assertSuccess(this); - } - - isSuccess(): boolean { - return this.getReturnCode().isSuccess(); - } - - getReturnCode(): ReturnCode { - let tokens = this.getDataTokens(); - if (tokens.length < 2) { - return ReturnCode.None; - } - let returnCodeToken = tokens[1]; - return ReturnCode.fromBuffer(returnCodeToken); - } - - outputUntyped(): Buffer[] { - this.assertSuccess(); - - // Skip the first 2 SCRs (eg. the @6f6b from @6f6b@2b). - return this.getDataTokens().slice(2); - } - - setEndpointDefinition(endpointDefinition: EndpointDefinition) { - this.endpointDefinition = endpointDefinition; - } - - getEndpointDefinition(): EndpointDefinition | undefined { - return this.endpointDefinition; - } - - getReturnMessage(): string { - return this.returnMessage; - } - - outputTyped(): TypedValue[] { - return Result.outputTyped(this); - } - - unpackOutput(): any { - return Result.unpackOutput(this); - } -} diff --git a/src/smartcontracts/wrapper/contractLogger.ts b/src/smartcontracts/wrapper/contractLogger.ts index a9a13e60..a597dd2b 100644 --- a/src/smartcontracts/wrapper/contractLogger.ts +++ b/src/smartcontracts/wrapper/contractLogger.ts @@ -3,7 +3,8 @@ import { NetworkConfig } from "../../networkConfig"; import { Transaction } from "../../transaction"; import { Query } from "../query"; import { QueryResponse } from "../queryResponse"; -import { SmartContractResults, TypedResult } from "../smartContractResults"; +import { SmartContractResults } from "../smartContractResults"; +import { findImmediateResult, findResultingCalls, TypedResult } from "./deprecatedSCRs"; /** * Provides a simple interface in order to easily call or query the smart contract's methods. @@ -42,17 +43,17 @@ export class ContractLogger { } function logReturnMessages(transaction: Transaction, smartContractResults: SmartContractResults) { - let immediate = smartContractResults.getImmediate(); + let immediate = findImmediateResult(smartContractResults)!; logSmartContractResultIfMessage("(immediate)", transaction, immediate); - let resultingCalls = smartContractResults.getResultingCalls(); + let resultingCalls = findResultingCalls(smartContractResults); for (let i in resultingCalls) { logSmartContractResultIfMessage("(resulting call)", transaction, resultingCalls[i]); } } function logSmartContractResultIfMessage(info: string, _transaction: Transaction, smartContractResult: TypedResult) { - if (smartContractResult.returnMessage) { - console.log(`Return message ${info} message: ${smartContractResult.returnMessage}`); + if (smartContractResult.getReturnMessage()) { + console.log(`Return message ${info} message: ${smartContractResult.getReturnMessage()}`); } } diff --git a/src/smartcontracts/wrapper/contractWrapper.spec.ts b/src/smartcontracts/wrapper/contractWrapper.spec.ts index 4684e991..5b88eb03 100644 --- a/src/smartcontracts/wrapper/contractWrapper.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.spec.ts @@ -1,5 +1,4 @@ import { - AddImmediateResult, MarkNotarized, MockProvider, setupUnitTestWatcherTimeouts, @@ -109,11 +108,11 @@ function mockQuery(provider: MockProvider, functionName: string, mockedResult: s ); } -async function mockCall(provider: MockProvider, mockedResult: string, promise: Promise) { +async function mockCall(provider: MockProvider, _mockedResult: string, promise: Promise) { let [, value] = await Promise.all([ provider.mockNextTransactionTimeline([ new TransactionStatus("executed"), - new AddImmediateResult(mockedResult), + // TODO: Add SCRs (_mockedResult) new MarkNotarized(), ]), promise, diff --git a/src/smartcontracts/wrapper/contractWrapper.ts b/src/smartcontracts/wrapper/contractWrapper.ts index 6415fccc..bdca01a1 100644 --- a/src/smartcontracts/wrapper/contractWrapper.ts +++ b/src/smartcontracts/wrapper/contractWrapper.ts @@ -17,11 +17,12 @@ import { SmartContract } from "../smartContract"; import { Code } from "../code"; import { Transaction } from "../../transaction"; import { IProvider } from "../../interface"; -import { ExecutionResultsBundle } from "../interface"; import { Interaction } from "../interaction"; import { Err, ErrContract, ErrInvalidArgument } from "../../errors"; import { Egld } from "../../balanceBuilder"; import { Balance } from "../../balance"; +import { ExecutionResultsBundle, findImmediateResult, interpretExecutionResults } from "./deprecatedSCRs"; +import { Result } from "./result"; /** * Provides a simple interface in order to easily call or query the smart contract's methods. @@ -110,7 +111,7 @@ export class ContractWrapper extends ChainSendContext { let transactionOnNetwork = await this.processTransaction(transaction); let smartContractResults = transactionOnNetwork.getSmartContractResults(); - let immediateResult = smartContractResults.getImmediate(); + let immediateResult = findImmediateResult(smartContractResults)!; immediateResult.assertSuccess(); let logger = this.context.getLogger(); logger?.deployComplete(transaction, smartContractResults, this.smartContract.getAddress()); @@ -140,7 +141,7 @@ export class ContractWrapper extends ChainSendContext { let response = await provider.queryContract(query); console.log("got response...", response); let queryResponseBundle = interaction.interpretQueryResponse(response); - let result = queryResponseBundle.queryResponse.unpackOutput(); + let result = Result.unpackOutput(queryResponseBundle.queryResponse); logger?.queryComplete(result, response); return result; @@ -163,7 +164,7 @@ export class ContractWrapper extends ChainSendContext { interaction: Interaction }): Promise<{ executionResultsBundle: ExecutionResultsBundle, result: any }> { let transactionOnNetwork = await this.processTransaction(transaction); - let executionResultsBundle = interaction.interpretExecutionResults(transactionOnNetwork); + let executionResultsBundle = interpretExecutionResults(interaction.getEndpoint(), transactionOnNetwork); let { smartContractResults, immediateResult } = executionResultsBundle; let result = immediateResult?.unpackOutput(); let logger = this.context.getLogger(); @@ -232,7 +233,8 @@ export class ContractWrapper extends ChainSendContext { let executingFunction = preparedCall.formattedCall.getExecutingFunction(); let interpretingFunction = preparedCall.formattedCall.getInterpretingFunction(); let typedValueArgs = preparedCall.formattedCall.toTypedValues(); - let interaction = new Interaction(this.smartContract, executingFunction, interpretingFunction, typedValueArgs, preparedCall.receiver); + // TODO: Most probably, this need to be fixed. Perhaps two interactions have to be instantiated? + let interaction = new Interaction(this.smartContract, executingFunction, typedValueArgs, preparedCall.receiver); interaction.withValue(preparedCall.egldValue); return interaction; } diff --git a/src/smartcontracts/wrapper/deprecatedSCRs.ts b/src/smartcontracts/wrapper/deprecatedSCRs.ts new file mode 100644 index 00000000..f7e18a40 --- /dev/null +++ b/src/smartcontracts/wrapper/deprecatedSCRs.ts @@ -0,0 +1,134 @@ +import { TransactionOnNetwork } from "../../transactionOnNetwork"; +import { ReturnCode } from "../returnCode"; +import { SmartContractResultItem, SmartContractResults } from "../smartContractResults"; +import { EndpointDefinition, TypedValue } from "../typesystem"; +import { Result } from "./result"; + +/** + * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. + * The SCRs are more alike a graph. + */ +export function interpretExecutionResults(endpoint: EndpointDefinition, transactionOnNetwork: TransactionOnNetwork): ExecutionResultsBundle { + let smartContractResults = transactionOnNetwork.getSmartContractResults(); + let immediateResult = findImmediateResult(smartContractResults)!; + let resultingCalls = findResultingCalls(smartContractResults); + + immediateResult.setEndpointDefinition(endpoint); + + let values = immediateResult.outputTyped(); + let returnCode = immediateResult.getReturnCode(); + + return { + transactionOnNetwork: transactionOnNetwork, + smartContractResults: smartContractResults, + immediateResult, + resultingCalls, + values, + firstValue: values[0], + returnCode: returnCode + }; +} + +/** + * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. + * The SCRs are more alike a graph. + */ +export interface ExecutionResultsBundle { + transactionOnNetwork: TransactionOnNetwork; + smartContractResults: SmartContractResults; + immediateResult: TypedResult; + resultingCalls: TypedResult[]; + values: TypedValue[]; + firstValue: TypedValue; + returnCode: ReturnCode; +} + +/** + * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. + * The SCRs are more alike a graph. + */ +export function findImmediateResult(results: SmartContractResults): TypedResult | undefined { + let immediateItem = results.getAll().filter(item => isImmediateResult(item))[0]; + if (immediateItem) { + return new TypedResult(immediateItem); + } + return undefined; +} + +/** + * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. + * The SCRs are more alike a graph. + */ +export function findResultingCalls(results: SmartContractResults): TypedResult[] { + let otherItems = results.getAll().filter(item => !isImmediateResult(item)); + let resultingCalls = otherItems.map(item => new TypedResult(item)); + return resultingCalls; +} + +/** + * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. + * The SCRs are more alike a graph. + */ +function isImmediateResult(item: SmartContractResultItem): boolean { + return item.nonce.valueOf() != 0; +} + +/** + * @deprecated getReturnCode(), outputUntyped are a bit fragile. + * They are not necessarily applicable to SCRs, in general (only in particular). + */ +export class TypedResult extends SmartContractResultItem implements Result.IResult { + /** + * If available, will provide typed output arguments (with typed values). + */ + endpointDefinition?: EndpointDefinition; + + constructor(init?: Partial) { + super(); + Object.assign(this, init); + } + + assertSuccess() { + Result.assertSuccess(this); + } + + isSuccess(): boolean { + return this.getReturnCode().isSuccess(); + } + + getReturnCode(): ReturnCode { + let tokens = this.getDataTokens(); + if (tokens.length < 2) { + return ReturnCode.None; + } + let returnCodeToken = tokens[1]; + return ReturnCode.fromBuffer(returnCodeToken); + } + + outputUntyped(): Buffer[] { + this.assertSuccess(); + + // Skip the first 2 SCRs (eg. the @6f6b from @6f6b@2b). + return this.getDataTokens().slice(2); + } + + setEndpointDefinition(endpointDefinition: EndpointDefinition) { + this.endpointDefinition = endpointDefinition; + } + + getEndpointDefinition(): EndpointDefinition | undefined { + return this.endpointDefinition; + } + + getReturnMessage(): string { + return "TODO: the return message isn't available on SmartContractResultItem (not provided by the API)"; + } + + outputTyped(): TypedValue[] { + return Result.outputTyped(this); + } + + unpackOutput(): any { + return Result.unpackOutput(this); + } +} \ No newline at end of file diff --git a/src/smartcontracts/result.ts b/src/smartcontracts/wrapper/result.ts similarity index 83% rename from src/smartcontracts/result.ts rename to src/smartcontracts/wrapper/result.ts index 613c4e0f..9f601a97 100644 --- a/src/smartcontracts/result.ts +++ b/src/smartcontracts/wrapper/result.ts @@ -1,8 +1,8 @@ -import { ErrContract } from "../errors"; -import { guardValueIsSet } from "../utils"; -import { ArgSerializer } from "./argSerializer"; -import { ReturnCode } from "./returnCode"; -import { EndpointDefinition, TypedValue } from "./typesystem"; +import { ErrContract } from "../../errors"; +import { guardValueIsSet } from "../../utils"; +import { ArgSerializer } from "../argSerializer"; +import { ReturnCode } from "../returnCode"; +import { EndpointDefinition, TypedValue } from "../typesystem"; export namespace Result { @@ -15,7 +15,6 @@ export namespace Result { assertSuccess(): void; outputUntyped(): Buffer[]; outputTyped(): TypedValue[]; - unpackOutput(): any; } export function isSuccess(result: IResult): boolean { diff --git a/src/testutils/mockProvider.ts b/src/testutils/mockProvider.ts index ee6ff0d4..cb43b42b 100644 --- a/src/testutils/mockProvider.ts +++ b/src/testutils/mockProvider.ts @@ -118,10 +118,6 @@ export class MockProvider implements IProvider { transaction.hyperblockNonce = new Nonce(42); transaction.hyperblockHash = new Hash("a".repeat(32)); }); - } else if (point instanceof AddImmediateResult) { - this.mockUpdateTransaction(hash, (transaction) => { - transaction.getSmartContractResults().getImmediate().data = point.data; - }); } else if (point instanceof Wait) { await timeline.start(point.milliseconds); } @@ -221,14 +217,6 @@ export class Wait { export class MarkNotarized { } -export class AddImmediateResult { - readonly data: string; - - constructor(data: string) { - this.data = data; - } -} - class QueryResponder { readonly matches: (query: Query) => boolean; readonly response: QueryResponse; From c4a28aecdcd33c17f671d473237b3bfa17e944c2 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sat, 19 Mar 2022 00:47:28 +0200 Subject: [PATCH 04/37] WIP. --- src/smartcontracts/index.ts | 2 +- .../interaction.local.net.spec.ts | 61 +++++++++---------- src/smartcontracts/interaction.ts | 8 +-- ...ker.spec.ts => interactionChecker.spec.ts} | 4 +- ...strictChecker.ts => interactionChecker.ts} | 3 +- src/smartcontracts/interface.ts | 4 -- .../smartContract.local.net.spec.ts | 4 +- src/smartcontracts/wrapper/sendContext.ts | 4 -- 8 files changed, 40 insertions(+), 50 deletions(-) rename src/smartcontracts/{strictChecker.spec.ts => interactionChecker.spec.ts} (95%) rename src/smartcontracts/{strictChecker.ts => interactionChecker.ts} (95%) diff --git a/src/smartcontracts/index.ts b/src/smartcontracts/index.ts index 50bcd518..1ea26392 100644 --- a/src/smartcontracts/index.ts +++ b/src/smartcontracts/index.ts @@ -18,7 +18,7 @@ export * from "./queryResponse"; export * from "./returnCode"; export * from "./smartContract"; export * from "./smartContractResults"; -export * from "./strictChecker"; +export * from "./interactionChecker"; export * from "./transactionPayloadBuilders"; export * from "./typesystem"; export * from "./wrapper"; diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 11807b7f..ec2a1e45 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -1,4 +1,3 @@ -import { StrictChecker } from "./strictChecker"; import { DefaultInteractionRunner } from "./defaultRunner"; import { SmartContract } from "./smartContract"; import { BigUIntValue, OptionValue, TypedValue, U32Value } from "./typesystem"; @@ -52,11 +51,11 @@ describe("test smart contract interactor", function () { // Execute, and wait for execution transaction = interaction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(transaction); - let executionResultsBundle = await runner.run(transaction, interaction); + let transactionOnNetwork = await runner.run(transaction); - assert.lengthOf(executionResultsBundle.values, 1); - assert.deepEqual(executionResultsBundle.firstValue.valueOf(), new BigNumber(42)); - assert.isTrue(executionResultsBundle.returnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(executionResultsBundle.values, 1); + // TODO: assert.deepEqual(executionResultsBundle.firstValue.valueOf(), new BigNumber(42)); + // TODO: assert.isTrue(executionResultsBundle.returnCode.equals(ReturnCode.Ok)); }); it("should interact with 'counter' (local testnet)", async function () { @@ -83,8 +82,8 @@ describe("test smart contract interactor", function () { // Increment, wait for execution. let incrementTransaction = incrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(incrementTransaction); - let { firstValue: valueAfterIncrement } = await runner.run(incrementTransaction, incrementInteraction); - assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(2)); + let transactionOnNetwork = await runner.run(incrementTransaction); + // TODO: assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(2)); // Decrement twice. Wait for execution of the second transaction. let decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); @@ -93,8 +92,8 @@ describe("test smart contract interactor", function () { decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(decrementTransaction); - let { firstValue: valueAfterDecrement } = await runner.run(decrementTransaction, decrementInteraction); - assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(0)); + transactionOnNetwork = await runner.run(decrementTransaction); + // TODO: assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(0)); }); it("should interact with 'lottery_egld' (local testnet)", async function () { @@ -131,38 +130,38 @@ describe("test smart contract interactor", function () { // start() let startTransaction = startInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(startTransaction); - let { returnCode: startReturnCode, values: startReturnvalues } = await runner.run(startTransaction, startInteraction); - assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); - assert.lengthOf(startReturnvalues, 0); + let transactionOnNetwork = await runner.run(startTransaction); + // TODO: assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(startReturnvalues, 0); // status() let statusTransaction = lotteryStatusInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(statusTransaction); - let { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } = await runner.run(statusTransaction, lotteryStatusInteraction); - assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); - assert.lengthOf(statusReturnValues, 1); - assert.equal(statusFirstValue.valueOf().name, "Running"); + transactionOnNetwork = await runner.run(statusTransaction); + // TODO: assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(statusReturnValues, 1); + // TODO: assert.equal(statusFirstValue.valueOf().name, "Running"); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) let lotteryInfoTransaction = getLotteryInfoInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(lotteryInfoTransaction); - let { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue } = await runner.run(lotteryInfoTransaction, getLotteryInfoInteraction); - assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); - assert.lengthOf(infoReturnValues, 1); + transactionOnNetwork = await runner.run(lotteryInfoTransaction); + // TODO: assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); + // TODO: assert.lengthOf(infoReturnValues, 1); // Ignore "deadline" field in our test - let info = infoFirstValue.valueOf(); - delete info.deadline; - - assert.deepEqual(info, { - ticket_price: new BigNumber("1000000000000000000"), - tickets_left: new BigNumber(800), - max_entries_per_user: new BigNumber(1), - prize_distribution: Buffer.from([0x64]), - whitelist: [], - current_ticket_number: new BigNumber(0), - prize_pool: new BigNumber("0") - }); + // TODO let info = infoFirstValue.valueOf(); + // delete info.deadline; + + // TODO assert.deepEqual(info, { + // ticket_price: new BigNumber("1000000000000000000"), + // tickets_left: new BigNumber(800), + // max_entries_per_user: new BigNumber(1), + // prize_distribution: Buffer.from([0x64]), + // whitelist: [], + // current_ticket_number: new BigNumber(0), + // prize_pool: new BigNumber("0") + // }); }); /** diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index 99ecc097..fb7861c0 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -8,10 +8,10 @@ import { Address } from "../address"; import { SmartContract } from "./smartContract"; import { AddressValue, BigUIntValue, BytesValue, EndpointDefinition, TypedValue, U64Value, U8Value } from "./typesystem"; import { Nonce } from "../nonce"; -import { IInteractionChecker, QueryResponseBundle } from "./interface"; +import { QueryResponseBundle } from "./interface"; import { NetworkConfig } from "../networkConfig"; import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; -import { StrictChecker } from "./strictChecker"; +import { InteractionChecker } from "./interactionChecker"; import { Account } from "../account"; /** @@ -21,7 +21,7 @@ import { Account } from "../account"; * the execution outcome for the objects they've built. */ export class Interaction { - private readonly checker: IInteractionChecker; + private readonly checker: InteractionChecker; private readonly contract: SmartContract; private readonly function: ContractFunction; private readonly args: TypedValue[]; @@ -44,7 +44,7 @@ export class Interaction { args: TypedValue[], receiver?: Address, ) { - this.checker = new StrictChecker(); + this.checker = new InteractionChecker(); this.contract = contract; this.function = func; this.args = args; diff --git a/src/smartcontracts/strictChecker.spec.ts b/src/smartcontracts/interactionChecker.spec.ts similarity index 95% rename from src/smartcontracts/strictChecker.spec.ts rename to src/smartcontracts/interactionChecker.spec.ts index 2395cdf2..8931cb18 100644 --- a/src/smartcontracts/strictChecker.spec.ts +++ b/src/smartcontracts/interactionChecker.spec.ts @@ -1,5 +1,5 @@ import * as errors from "../errors"; -import { StrictChecker as StrictInteractionChecker } from "./strictChecker"; +import { InteractionChecker } from "./interactionChecker"; import { SmartContract } from "./smartContract"; import { BigUIntValue, OptionValue, U64Value } from "./typesystem"; import { loadAbiRegistry } from "../testutils"; @@ -13,7 +13,7 @@ import { BytesValue } from "./typesystem/bytes"; describe("integration tests: test checker within interactor", function () { let dummyAddress = new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); - let checker = new StrictInteractionChecker(); + let checker = new InteractionChecker(); it("should detect errors for 'ultimate answer'", async function () { let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); diff --git a/src/smartcontracts/strictChecker.ts b/src/smartcontracts/interactionChecker.ts similarity index 95% rename from src/smartcontracts/strictChecker.ts rename to src/smartcontracts/interactionChecker.ts index 928bd8db..f38e6e25 100644 --- a/src/smartcontracts/strictChecker.ts +++ b/src/smartcontracts/interactionChecker.ts @@ -1,7 +1,6 @@ import * as errors from "../errors"; import { EndpointDefinition } from "./typesystem"; import { Interaction } from "./interaction"; -import { IInteractionChecker } from "./interface"; /** * An interaction checker that aims to be as strict as possible. @@ -10,7 +9,7 @@ import { IInteractionChecker } from "./interface"; * - errors related to calling "non-payable" functions with some value provided * - gas estimation errors (not yet implemented) */ -export class StrictChecker implements IInteractionChecker { +export class InteractionChecker { checkInteraction(interaction: Interaction): void { let definition = interaction.getEndpoint(); diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index 456f4815..d7cd65de 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -35,10 +35,6 @@ export interface ISmartContract { call({ func, args, value, gasLimit }: CallArguments): Transaction; } -export interface IInteractionChecker { - checkInteraction(interaction: Interaction): void; -} - export interface DeployArguments { code: Code; codeMetadata?: CodeMetadata; diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 750ac5b9..3733037f 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -269,10 +269,10 @@ describe("test on local testnet", function () { // Let's check the SCRs let deployResults = (await transactionDeploy.getAsOnNetwork(provider)).getSmartContractResults(); - deployResults.getImmediate().assertSuccess(); + // TODO: deployResults.getImmediate().assertSuccess(); let startResults = (await transactionStart.getAsOnNetwork(provider)).getSmartContractResults(); - startResults.getImmediate().assertSuccess(); + // TODO: startResults.getImmediate().assertSuccess(); // Query state, do some assertions let queryResponse = await contract.runQuery(provider, { diff --git a/src/smartcontracts/wrapper/sendContext.ts b/src/smartcontracts/wrapper/sendContext.ts index 99390b9f..9eb53913 100644 --- a/src/smartcontracts/wrapper/sendContext.ts +++ b/src/smartcontracts/wrapper/sendContext.ts @@ -1,7 +1,5 @@ import { GasLimit } from "../../networkParams"; -import { IInteractionChecker } from "../interface"; import { IProvider } from "../../interface"; -import { StrictChecker } from "../strictChecker"; import { ContractLogger } from "./contractLogger"; import { TestWallet } from "../../testutils"; import { Balance } from "../../balance"; @@ -17,7 +15,6 @@ export class SendContext { private gas_: GasLimit | null; private logger_: ContractLogger | null; private value_: Balance | null; - readonly checker: IInteractionChecker; constructor(provider: IProvider) { this.sender_ = null; @@ -25,7 +22,6 @@ export class SendContext { this.gas_ = null; this.logger_ = null; this.value_ = null; - this.checker = new StrictChecker(); } provider(provider: IProvider): this { From 97aef5a41d0fdc3b878bf63d56e21b674c42fdc7 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 11:16:23 +0200 Subject: [PATCH 05/37] Improve mock provider. --- src/testutils/mockProvider.ts | 63 +++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/testutils/mockProvider.ts b/src/testutils/mockProvider.ts index cb43b42b..a9a12664 100644 --- a/src/testutils/mockProvider.ts +++ b/src/testutils/mockProvider.ts @@ -15,6 +15,10 @@ import { NetworkStatus } from "../networkStatus"; import { TypedEvent } from "../events"; import { BalanceBuilder } from "../balanceBuilder"; import BigNumber from "bignumber.js"; +import { SmartContractResultItem, SmartContractResults } from "../smartcontracts"; + +const DummyHyperblockNonce = new Nonce(42); +const DummyHyperblockHash = new Hash("a".repeat(32)); /** * A mock {@link IProvider}, used for tests only. @@ -27,7 +31,8 @@ export class MockProvider implements IProvider { private readonly transactions: Map; private readonly onTransactionSent: TypedEvent<{ transaction: Transaction }>; private readonly accounts: Map; - private readonly queryResponders: QueryResponder[] = []; + private readonly queryContractResponders: QueryContractResponder[] = []; + private readonly getTransactionResponders: GetTransactionResponder[] = []; constructor() { this.transactions = new Map(); @@ -78,13 +83,21 @@ export class MockProvider implements IProvider { this.transactions.set(hash.toString(), item); } - mockQueryResponseOnFunction(functionName: string, response: QueryResponse) { + mockQueryContractOnFunction(functionName: string, response: QueryResponse) { let predicate = (query: Query) => query.func.name == functionName; - this.queryResponders.push(new QueryResponder(predicate, response)); + this.queryContractResponders.push(new QueryContractResponder(predicate, response)); } - mockQueryResponse(predicate: (query: Query) => boolean, response: QueryResponse) { - this.queryResponders.push(new QueryResponder(predicate, response)); + mockGetTransactionWithAnyHashAsNotarizedWithContractResults(contractResults: SmartContractResultItem[]) { + let predicate = (_hash: TransactionHash) => true; + let response = new TransactionOnNetwork({ + status: new TransactionStatus("executed"), + hyperblockNonce: DummyHyperblockNonce, + hyperblockHash: DummyHyperblockHash, + results: new SmartContractResults(contractResults) + }); + + this.getTransactionResponders.push(new GetTransactionResponder(predicate, response)); } async mockTransactionTimeline(transaction: Transaction, timelinePoints: any[]): Promise { @@ -97,7 +110,7 @@ export class MockProvider implements IProvider { return this.mockTransactionTimelineByHash(transaction.getHash(), timelinePoints); } - async nextTransactionSent(): Promise { + private async nextTransactionSent(): Promise { return new Promise((resolve, _reject) => { this.onTransactionSent.on((eventArgs) => resolve(eventArgs.transaction)); }); @@ -115,8 +128,8 @@ export class MockProvider implements IProvider { }); } else if (point instanceof MarkNotarized) { this.mockUpdateTransaction(hash, (transaction) => { - transaction.hyperblockNonce = new Nonce(42); - transaction.hyperblockHash = new Hash("a".repeat(32)); + transaction.hyperblockNonce = DummyHyperblockNonce; + transaction.hyperblockHash = DummyHyperblockHash; }); } else if (point instanceof Wait) { await timeline.start(point.milliseconds); @@ -171,6 +184,14 @@ export class MockProvider implements IProvider { _hintSender?: Address, _withResults?: boolean ): Promise { + // At first, try to use a mock responder + for (const responder of this.getTransactionResponders) { + if (responder.matches(txHash)) { + return responder.response; + } + } + + // Then, try to use the local collection of transactions let transaction = this.transactions.get(txHash.toString()); if (transaction) { return transaction; @@ -180,12 +201,8 @@ export class MockProvider implements IProvider { } async getTransactionStatus(txHash: TransactionHash): Promise { - let transaction = this.transactions.get(txHash.toString()); - if (transaction) { - return transaction.status; - } - - throw new errors.ErrMock("Transaction not found"); + let transaction = await this.getTransaction(txHash); + return transaction.status; } async getNetworkConfig(): Promise { @@ -197,7 +214,7 @@ export class MockProvider implements IProvider { } async queryContract(query: Query): Promise { - for (const responder of this.queryResponders) { + for (const responder of this.queryContractResponders) { if (responder.matches(query)) { return responder.response; } @@ -217,12 +234,22 @@ export class Wait { export class MarkNotarized { } -class QueryResponder { +class QueryContractResponder { readonly matches: (query: Query) => boolean; readonly response: QueryResponse; constructor(matches: (query: Query) => boolean, response: QueryResponse) { - this.matches = matches || ((_) => true); - this.response = response || new QueryResponse(); + this.matches = matches; + this.response = response; + } +} + +class GetTransactionResponder { + readonly matches: (hash: TransactionHash) => boolean; + readonly response: TransactionOnNetwork; + + constructor(matches: (hash: TransactionHash) => boolean, response: TransactionOnNetwork) { + this.matches = matches; + this.response = response; } } From e9809b5a537a13dcc482228137938a60b93bb342 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 11:20:29 +0200 Subject: [PATCH 06/37] Refactor contract results (breaking change). --- src/networkProvider/contractResults.ts | 23 ++---------- src/networkProvider/interface.ts | 10 ++--- src/networkProvider/providers.dev.net.spec.ts | 6 +-- src/smartcontracts/interface.ts | 22 ++++++++--- src/smartcontracts/queryResponse.ts | 37 +++---------------- src/smartcontracts/returnCode.ts | 3 ++ .../smartContract.local.net.spec.ts | 18 ++++----- src/smartcontracts/smartContract.ts | 1 + src/smartcontracts/smartContractResults.ts | 6 ++- .../wrapper/contractWrapper.spec.ts | 2 +- src/transactionOnNetwork.ts | 20 +++------- 11 files changed, 55 insertions(+), 93 deletions(-) diff --git a/src/networkProvider/contractResults.ts b/src/networkProvider/contractResults.ts index c4b0126a..8cebbb3e 100644 --- a/src/networkProvider/contractResults.ts +++ b/src/networkProvider/contractResults.ts @@ -5,7 +5,7 @@ import { Hash } from "../hash"; import { IContractQueryResponse, IContractResultItem, IContractResults } from "./interface"; import { GasLimit, GasPrice } from "../networkParams"; import { Nonce } from "../nonce"; -import { ArgSerializer, EndpointDefinition, MaxUint64, ReturnCode, TypedValue } from "../smartcontracts"; +import { MaxUint64, ReturnCode } from "../smartcontracts"; import { TransactionHash } from "../transaction"; export class ContractResults implements IContractResults { @@ -80,16 +80,6 @@ export class ContractResultItem implements IContractResultItem { return item; } - - getOutputUntyped(): Buffer[] { - // TODO: Decide how to parse "data" (immediate results vs. other results). - throw new Error("Method not implemented."); - } - - getOutputTyped(_endpointDefinition: EndpointDefinition): TypedValue[] { - // TODO: Decide how to parse "data" (immediate results vs. other results). - throw new Error("Method not implemented."); - } } export class ContractQueryResponse implements IContractQueryResponse { @@ -110,14 +100,7 @@ export class ContractQueryResponse implements IContractQueryResponse { return response; } - getOutputUntyped(): Buffer[] { - let buffers = this.returnData.map((item) => Buffer.from(item || "", "base64")); - return buffers; - } - - getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[] { - let buffers = this.getOutputUntyped(); - let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition!.output); - return values; + getReturnDataParts(): Buffer[] { + return this.returnData.map((item) => Buffer.from(item || "")); } } diff --git a/src/networkProvider/interface.ts b/src/networkProvider/interface.ts index 287fd791..4f21dbe4 100644 --- a/src/networkProvider/interface.ts +++ b/src/networkProvider/interface.ts @@ -9,7 +9,7 @@ import { NetworkStake } from "../networkStake"; import { NetworkStatus } from "../networkStatus"; import { Nonce } from "../nonce"; import { Signature } from "../signature"; -import { EndpointDefinition, Query, ReturnCode, TypedValue } from "../smartcontracts"; +import { Query, ReturnCode } from "../smartcontracts"; import { Stats } from "../stats"; import { Transaction, TransactionHash, TransactionStatus } from "../transaction"; import { TransactionLogs } from "../transactionLogs"; @@ -204,9 +204,6 @@ export interface IContractResultItem { gasPrice: GasPrice; callType: number; returnMessage: string; - - getOutputUntyped(): Buffer[]; - getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[]; } export interface IContractQueryResponse { @@ -214,9 +211,8 @@ export interface IContractQueryResponse { returnCode: ReturnCode; returnMessage: string; gasUsed: GasLimit; - - getOutputUntyped(): Buffer[]; - getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[]; + + getReturnDataParts(): Buffer[]; } export interface IContractSimulation { diff --git a/src/networkProvider/providers.dev.net.spec.ts b/src/networkProvider/providers.dev.net.spec.ts index 8ee0031b..affbd8d0 100644 --- a/src/networkProvider/providers.dev.net.spec.ts +++ b/src/networkProvider/providers.dev.net.spec.ts @@ -227,7 +227,7 @@ describe("test network providers on devnet: Proxy and API", function () { let proxyResponse = await proxyProvider.queryContract(query); assert.deepEqual(apiResponse, proxyResponse); - assert.deepEqual(apiResponse.getOutputUntyped(), proxyResponse.getOutputUntyped()); + assert.deepEqual(apiResponse.getReturnDataParts(), proxyResponse.getReturnDataParts()); // Query: increment counter query = new Query({ @@ -240,7 +240,7 @@ describe("test network providers on devnet: Proxy and API", function () { proxyResponse = await proxyProvider.queryContract(query); assert.deepEqual(apiResponse, proxyResponse); - assert.deepEqual(apiResponse.getOutputUntyped(), proxyResponse.getOutputUntyped()); + assert.deepEqual(apiResponse.getReturnDataParts(), proxyResponse.getReturnDataParts()); // Query: issue ESDT query = new Query({ @@ -260,6 +260,6 @@ describe("test network providers on devnet: Proxy and API", function () { proxyResponse = await proxyProvider.queryContract(query); assert.deepEqual(apiResponse, proxyResponse); - assert.deepEqual(apiResponse.getOutputUntyped(), proxyResponse.getOutputUntyped()); + assert.deepEqual(apiResponse.getReturnDataParts(), proxyResponse.getReturnDataParts()); }); }); diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index d7cd65de..f92d1f99 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -2,13 +2,14 @@ import { Address } from "../address"; import { Balance } from "../balance"; import { GasLimit } from "../networkParams"; import { Transaction } from "../transaction"; +import { TransactionOnNetwork } from "../transactionOnNetwork"; import { Code } from "./code"; import { CodeMetadata } from "./codeMetadata"; import { ContractFunction } from "./function"; import { Interaction } from "./interaction"; import { QueryResponse } from "./queryResponse"; import { ReturnCode } from "./returnCode"; -import { TypedValue } from "./typesystem"; +import { EndpointDefinition, TypedValue } from "./typesystem"; /** * ISmartContract defines a general interface for operating with {@link SmartContract} objects. @@ -66,9 +67,20 @@ export interface QueryArguments { caller?: Address } -export interface QueryResponseBundle { - queryResponse: QueryResponse; - firstValue: TypedValue; - values: TypedValue[]; +export interface ContractOutcomeBundle { returnCode: ReturnCode; + returnMessage: string; + values: TypedValue[]; + firstValue?: TypedValue; + secondValue?: TypedValue; + thirdValue?: TypedValue; +} + +export interface IInteractionChecker { + checkInteraction(interaction: Interaction, definition: EndpointDefinition): void; +} + +export interface IResultsParser { + parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): ContractOutcomeBundle; + parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): ContractOutcomeBundle } diff --git a/src/smartcontracts/queryResponse.ts b/src/smartcontracts/queryResponse.ts index 64e9ff00..847113a8 100644 --- a/src/smartcontracts/queryResponse.ts +++ b/src/smartcontracts/queryResponse.ts @@ -1,18 +1,11 @@ import { GasLimit } from "../networkParams"; -import { EndpointDefinition, TypedValue } from "./typesystem"; import { MaxUint64 } from "./query"; import { ReturnCode } from "./returnCode"; import BigNumber from "bignumber.js"; import { ErrContract } from "../errors"; -import { guardValueIsSet } from "../utils"; import { ArgSerializer } from "./argSerializer"; export class QueryResponse { - /** - * If available, will provide typed output arguments (with typed values). - */ - private endpointDefinition?: EndpointDefinition; - returnData: string[]; returnCode: ReturnCode; returnMessage: string; @@ -43,16 +36,18 @@ export class QueryResponse { }); } - getEndpointDefinition(): EndpointDefinition | undefined { - return this.endpointDefinition; - } getReturnCode(): ReturnCode { return this.returnCode; } + getReturnMessage(): string { return this.returnMessage; } + getReturnDataParts(): Buffer[] { + return this.returnData.map((item) => Buffer.from(item || "", "base64")); + } + assertSuccess() { if (this.isSuccess()) { return; @@ -65,28 +60,6 @@ export class QueryResponse { return this.returnCode.isSuccess(); } - setEndpointDefinition(endpointDefinition: EndpointDefinition): void { - this.endpointDefinition = endpointDefinition; - } - - outputUntyped(): Buffer[] { - this.assertSuccess(); - - let buffers = this.returnData.map((item) => Buffer.from(item || "", "base64")); - return buffers; - } - - outputTyped(): TypedValue[] { - this.assertSuccess(); - - let endpointDefinition = this.getEndpointDefinition(); - guardValueIsSet("endpointDefinition", endpointDefinition); - - let buffers = this.outputUntyped(); - let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition!.output); - return values; - } - /** * Converts the object to a pretty, plain JavaScript object. */ diff --git a/src/smartcontracts/returnCode.ts b/src/smartcontracts/returnCode.ts index b537062e..9d41015b 100644 --- a/src/smartcontracts/returnCode.ts +++ b/src/smartcontracts/returnCode.ts @@ -1,3 +1,6 @@ +/** + * Also see: https://github.com/ElrondNetwork/elrond-vm-common/blob/master/returnCodes.go + */ export class ReturnCode { static None = new ReturnCode(""); static Ok = new ReturnCode("ok"); diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 3733037f..b57da311 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -135,7 +135,7 @@ describe("test on local testnet", function () { // Check counter let queryResponse = await contract.runQuery(provider, { func: new ContractFunction("get") }); - assert.equal(3, decodeUnsignedNumber(queryResponse.outputUntyped()[0])); + assert.equal(3, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); }); it("erc20: should deploy, call and query contract", async function () { @@ -196,25 +196,25 @@ describe("test on local testnet", function () { let queryResponse = await contract.runQuery(provider, { func: new ContractFunction("totalSupply") }); - assert.equal(10000, decodeUnsignedNumber(queryResponse.outputUntyped()[0])); + assert.equal(10000, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); queryResponse = await contract.runQuery(provider, { func: new ContractFunction("balanceOf"), args: [new AddressValue(alice.address)] }); - assert.equal(7500, decodeUnsignedNumber(queryResponse.outputUntyped()[0])); + assert.equal(7500, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); queryResponse = await contract.runQuery(provider, { func: new ContractFunction("balanceOf"), args: [new AddressValue(bob.address)] }); - assert.equal(1000, decodeUnsignedNumber(queryResponse.outputUntyped()[0])); + assert.equal(1000, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); queryResponse = await contract.runQuery(provider, { func: new ContractFunction("balanceOf"), args: [new AddressValue(carol.address)] }); - assert.equal(1500, decodeUnsignedNumber(queryResponse.outputUntyped()[0])); + assert.equal(1500, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); }); it("lottery: should deploy, call and query contract", async function () { @@ -268,10 +268,10 @@ describe("test on local testnet", function () { await transactionStart.awaitNotarized(provider); // Let's check the SCRs - let deployResults = (await transactionDeploy.getAsOnNetwork(provider)).getSmartContractResults(); + let deployResults = (await transactionDeploy.getAsOnNetwork(provider)).results; // TODO: deployResults.getImmediate().assertSuccess(); - let startResults = (await transactionStart.getAsOnNetwork(provider)).getSmartContractResults(); + let startResults = (await transactionStart.getAsOnNetwork(provider)).results; // TODO: startResults.getImmediate().assertSuccess(); // Query state, do some assertions @@ -281,7 +281,7 @@ describe("test on local testnet", function () { BytesValue.fromUTF8("foobar") ] }); - assert.equal(decodeUnsignedNumber(queryResponse.outputUntyped()[0]), 1); + assert.equal(decodeUnsignedNumber(queryResponse.getReturnDataParts()[0]), 1); queryResponse = await contract.runQuery(provider, { func: new ContractFunction("status"), @@ -289,6 +289,6 @@ describe("test on local testnet", function () { BytesValue.fromUTF8("missingLottery") ] }); - assert.equal(decodeUnsignedNumber(queryResponse.outputUntyped()[0]), 0); + assert.equal(decodeUnsignedNumber(queryResponse.getReturnDataParts()[0]), 0); }); }); diff --git a/src/smartcontracts/smartContract.ts b/src/smartcontracts/smartContract.ts index 54941470..43fe345e 100644 --- a/src/smartcontracts/smartContract.ts +++ b/src/smartcontracts/smartContract.ts @@ -28,6 +28,7 @@ export class SmartContract implements ISmartContract { private code: Code = Code.nothing(); private codeMetadata: CodeMetadata = new CodeMetadata(); private abi?: SmartContractAbi; + // TODO: Perhaps remove this? private readonly trackOfTransactions: Transaction[] = []; /** diff --git a/src/smartcontracts/smartContractResults.ts b/src/smartcontracts/smartContractResults.ts index c2b6bda4..4e5afc18 100644 --- a/src/smartcontracts/smartContractResults.ts +++ b/src/smartcontracts/smartContractResults.ts @@ -28,6 +28,10 @@ export class SmartContractResults { } export class SmartContractResultItem { + constructor(init?: Partial) { + Object.assign(this, init); + } + hash: Hash = Hash.empty(); nonce: Nonce = new Nonce(0); value: Balance = Balance.Zero(); @@ -71,7 +75,7 @@ export class SmartContractResultItem { return item; } - getDataTokens(): Buffer[] { + getDataParts(): Buffer[] { return new ArgSerializer().stringToBuffers(this.data); } } diff --git a/src/smartcontracts/wrapper/contractWrapper.spec.ts b/src/smartcontracts/wrapper/contractWrapper.spec.ts index 5b88eb03..84f5d2fc 100644 --- a/src/smartcontracts/wrapper/contractWrapper.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.spec.ts @@ -102,7 +102,7 @@ describe("test smart contract wrapper", async function() { }); function mockQuery(provider: MockProvider, functionName: string, mockedResult: string) { - provider.mockQueryResponseOnFunction( + provider.mockQueryContractOnFunction( functionName, new QueryResponse({ returnData: [mockedResult], returnCode: ReturnCode.Ok }) ); diff --git a/src/transactionOnNetwork.ts b/src/transactionOnNetwork.ts index 6d913145..4cd75c20 100644 --- a/src/transactionOnNetwork.ts +++ b/src/transactionOnNetwork.ts @@ -32,9 +32,10 @@ export class TransactionOnNetwork { hyperblockNonce: Nonce = new Nonce(0); hyperblockHash: Hash = Hash.empty(); - private receipt: Receipt = new Receipt(); - private results: SmartContractResults = SmartContractResults.empty(); - private logs: TransactionLogs = TransactionLogs.empty(); + // TODO: Check if "receipt" is still received from the API. + receipt: Receipt = new Receipt(); + results: SmartContractResults = SmartContractResults.empty(); + logs: TransactionLogs = TransactionLogs.empty(); constructor(init?: Partial) { Object.assign(this, init); @@ -91,18 +92,6 @@ export class TransactionOnNetwork { getDateTime(): Date { return new Date(this.timestamp * 1000); } - - getReceipt(): Receipt { - return this.receipt; - } - - getSmartContractResults(): SmartContractResults { - return this.results; - } - - getLogs(): TransactionLogs { - return this.logs; - } } /** @@ -116,6 +105,7 @@ export class TransactionOnNetworkType { } } +// TODO: Check if we still need this. export class Receipt { value: Balance = Balance.Zero(); sender: Address = new Address(); From a9d23f689a9d7cc088b8460be25748ceedc46043 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 11:21:43 +0200 Subject: [PATCH 07/37] Define interaction controller. --- src/smartcontracts/defaultRunner.ts | 36 --- src/smartcontracts/index.ts | 2 +- .../interaction.local.net.spec.ts | 79 +++--- src/smartcontracts/interaction.spec.ts | 226 ++++++------------ src/smartcontracts/interaction.ts | 60 ++--- src/smartcontracts/interactionChecker.spec.ts | 10 +- src/smartcontracts/interactionChecker.ts | 11 +- src/smartcontracts/interactionController.ts | 85 +++++++ src/smartcontracts/resultsParser.ts | 57 +++++ 9 files changed, 286 insertions(+), 280 deletions(-) delete mode 100644 src/smartcontracts/defaultRunner.ts create mode 100644 src/smartcontracts/interactionController.ts create mode 100644 src/smartcontracts/resultsParser.ts diff --git a/src/smartcontracts/defaultRunner.ts b/src/smartcontracts/defaultRunner.ts deleted file mode 100644 index b3645bdc..00000000 --- a/src/smartcontracts/defaultRunner.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { IProvider } from "../interface"; -import { QueryResponseBundle } from "./interface"; -import { Interaction } from "./interaction"; -import { Transaction } from "../transaction"; -import { TransactionOnNetwork } from "../transactionOnNetwork"; - -/** - * An interaction runner, suitable for frontends and dApp, - * where signing is performed by means of an external wallet provider. - */ -export class DefaultInteractionRunner { - private readonly provider: IProvider; - - constructor(provider: IProvider) { - this.provider = provider; - } - - /** - * Broadcasts an alredy-signed interaction transaction, and also waits for its execution on the Network. - * - * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand - */ - async run(signedInteractionTransaction: Transaction): Promise { - await signedInteractionTransaction.send(this.provider); - await signedInteractionTransaction.awaitExecuted(this.provider); - return await signedInteractionTransaction.getAsOnNetwork(this.provider); - } - - async runQuery(interaction: Interaction): Promise { - let query = interaction.buildQuery(); - let response = await this.provider.queryContract(query); - // TODO: do not rely on interpretQueryResponse, as it may throw unexpectedly. - let bundle = interaction.interpretQueryResponse(response); - return bundle; - } -} diff --git a/src/smartcontracts/index.ts b/src/smartcontracts/index.ts index 1ea26392..e383e1cf 100644 --- a/src/smartcontracts/index.ts +++ b/src/smartcontracts/index.ts @@ -8,7 +8,7 @@ export * from "./argSerializer"; export * from "./code"; export * from "./codec"; export * from "./codeMetadata"; -export * from "./defaultRunner"; +export * from "./interactionController"; export * from "./function"; export * from "./interaction"; export * from "./interface"; diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index ec2a1e45..497516c8 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -1,4 +1,4 @@ -import { DefaultInteractionRunner } from "./defaultRunner"; +import { DefaultInteractionController } from "./interactionController"; import { SmartContract } from "./smartContract"; import { BigUIntValue, OptionValue, TypedValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadContractCode, loadTestWallets, TestWallet } from "../testutils"; @@ -17,10 +17,8 @@ import { chooseProxyProvider } from "../interactive"; describe("test smart contract interactor", function () { let provider = chooseProxyProvider("local-testnet"); let alice: TestWallet; - let runner: DefaultInteractionRunner; before(async function () { ({ alice } = await loadTestWallets()); - runner = new DefaultInteractionRunner(provider); }); it("should interact with 'answer' (local testnet)", async function () { @@ -29,6 +27,7 @@ describe("test smart contract interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["answer"]); let contract = new SmartContract({ abi: abi }); + let controller = new DefaultInteractionController(abi, provider); // Currently, this has to be called before creating any Interaction objects, // because the Transaction objects created under the hood point to the "default" NetworkConfig. @@ -39,9 +38,9 @@ describe("test smart contract interactor", function () { let interaction = contract.methods.getUltimateAnswer().withGasLimit(new GasLimit(3000000)); // Query - let queryResponseBundle = await runner.runQuery(interaction); + let queryResponseBundle = await controller.query(interaction); assert.lengthOf(queryResponseBundle.values, 1); - assert.deepEqual(queryResponseBundle.firstValue.valueOf(), new BigNumber(42)); + assert.deepEqual(queryResponseBundle.firstValue!.valueOf(), new BigNumber(42)); assert.isTrue(queryResponseBundle.returnCode.equals(ReturnCode.Ok)); // Execute, do not wait for execution @@ -51,11 +50,11 @@ describe("test smart contract interactor", function () { // Execute, and wait for execution transaction = interaction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(transaction); - let transactionOnNetwork = await runner.run(transaction); + let { bundle: executionResultsBundle } = await controller.execute(interaction, transaction); - // TODO: assert.lengthOf(executionResultsBundle.values, 1); - // TODO: assert.deepEqual(executionResultsBundle.firstValue.valueOf(), new BigNumber(42)); - // TODO: assert.isTrue(executionResultsBundle.returnCode.equals(ReturnCode.Ok)); + assert.lengthOf(executionResultsBundle.values, 1); + assert.deepEqual(executionResultsBundle.firstValue!.valueOf(), new BigNumber(42)); + assert.isTrue(executionResultsBundle.returnCode.equals(ReturnCode.Ok)); }); it("should interact with 'counter' (local testnet)", async function () { @@ -64,6 +63,7 @@ describe("test smart contract interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/counter.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["counter"]); let contract = new SmartContract({ abi: abi }); + let controller = new DefaultInteractionController(abi, provider); // Currently, this has to be called before creating any Interaction objects, // because the Transaction objects created under the hood point to the "default" NetworkConfig. @@ -76,14 +76,14 @@ describe("test smart contract interactor", function () { let decrementInteraction = (contract.methods.decrement()).withGasLimit(new GasLimit(3000000)); // Query "get()" - let { firstValue: counterValue } = await runner.runQuery(getInteraction); - assert.deepEqual(counterValue.valueOf(), new BigNumber(1)); + let { firstValue: counterValue } = await controller.query(getInteraction); + assert.deepEqual(counterValue!.valueOf(), new BigNumber(1)); // Increment, wait for execution. let incrementTransaction = incrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(incrementTransaction); - let transactionOnNetwork = await runner.run(incrementTransaction); - // TODO: assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(2)); + 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. let decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); @@ -92,8 +92,8 @@ describe("test smart contract interactor", function () { decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(decrementTransaction); - transactionOnNetwork = await runner.run(decrementTransaction); - // TODO: assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(0)); + let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); + assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(0)); }); it("should interact with 'lottery_egld' (local testnet)", async function () { @@ -102,6 +102,7 @@ describe("test smart contract interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/lottery_egld.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ abi: abi }); + let controller = new DefaultInteractionController(abi, provider); // Currently, this has to be called before creating any Interaction objects, // because the Transaction objects created under the hood point to the "default" NetworkConfig. @@ -130,38 +131,38 @@ describe("test smart contract interactor", function () { // start() let startTransaction = startInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(startTransaction); - let transactionOnNetwork = await runner.run(startTransaction); - // TODO: assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); - // TODO: assert.lengthOf(startReturnvalues, 0); + let { bundle: bundleStart } = await controller.execute(startInteraction, startTransaction); + assert.isTrue(bundleStart.returnCode.equals(ReturnCode.Ok)); + assert.lengthOf(bundleStart.values, 0); // status() - let statusTransaction = lotteryStatusInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); - await alice.signer.sign(statusTransaction); - transactionOnNetwork = await runner.run(statusTransaction); - // TODO: assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); - // TODO: assert.lengthOf(statusReturnValues, 1); - // TODO: assert.equal(statusFirstValue.valueOf().name, "Running"); + let lotteryStatusTransaction = lotteryStatusInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); + await alice.signer.sign(lotteryStatusTransaction); + let { bundle: bundleStatus } = await controller.execute(lotteryStatusInteraction, lotteryStatusTransaction); + assert.isTrue(bundleStatus.returnCode.equals(ReturnCode.Ok)); + assert.lengthOf(bundleStatus.values, 1); + assert.equal(bundleStatus.firstValue!.valueOf().name, "Running"); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) let lotteryInfoTransaction = getLotteryInfoInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await alice.signer.sign(lotteryInfoTransaction); - transactionOnNetwork = await runner.run(lotteryInfoTransaction); - // TODO: assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); - // TODO: assert.lengthOf(infoReturnValues, 1); + let { bundle: bundleLotteryInfo } = await controller.execute(getLotteryInfoInteraction, lotteryInfoTransaction); + assert.isTrue(bundleLotteryInfo.returnCode.equals(ReturnCode.Ok)); + assert.lengthOf(bundleLotteryInfo.values, 1); // Ignore "deadline" field in our test - // TODO let info = infoFirstValue.valueOf(); - // delete info.deadline; - - // TODO assert.deepEqual(info, { - // ticket_price: new BigNumber("1000000000000000000"), - // tickets_left: new BigNumber(800), - // max_entries_per_user: new BigNumber(1), - // prize_distribution: Buffer.from([0x64]), - // whitelist: [], - // current_ticket_number: new BigNumber(0), - // prize_pool: new BigNumber("0") - // }); + let info = bundleLotteryInfo.firstValue!.valueOf(); + delete info.deadline; + + assert.deepEqual(info, { + ticket_price: new BigNumber("1000000000000000000"), + tickets_left: new BigNumber(800), + max_entries_per_user: new BigNumber(1), + prize_distribution: Buffer.from([0x64]), + whitelist: [], + current_ticket_number: new BigNumber(0), + prize_pool: new BigNumber("0") + }); }); /** diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index c6317c96..e25c3724 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -1,10 +1,9 @@ -import { DefaultInteractionRunner } from "./defaultRunner"; +import { DefaultInteractionController } from "./interactionController"; import { SmartContract } from "./smartContract"; import { BigUIntValue, OptionValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadTestWallets, - MarkNotarized, MockProvider, setupUnitTestWatcherTimeouts, TestWallet, @@ -17,23 +16,21 @@ import { GasLimit } from "../networkParams"; import { ContractFunction } from "./function"; import { QueryResponse } from "./queryResponse"; import { Nonce } from "../nonce"; -import { TransactionStatus } from "../transaction"; import { ReturnCode } from "./returnCode"; import { Balance } from "../balance"; import BigNumber from "bignumber.js"; import { BytesValue } from "./typesystem/bytes"; import { Token, TokenType } from "../token"; import { createBalanceBuilder } from "../balanceBuilder"; +import { SmartContractResultItem } from "./smartContractResults"; describe("test smart contract interactor", function() { let dummyAddress = new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); let provider = new MockProvider(); let alice: TestWallet; - let runner: DefaultInteractionRunner; before(async function() { ({ alice } = await loadTestWallets()); - runner = new DefaultInteractionRunner(provider); }); it("should set transaction fields", async function () { @@ -116,28 +113,28 @@ describe("test smart contract interactor", function() { let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["answer"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); + let controller = new DefaultInteractionController(abi, provider); let interaction = contract.methods .getUltimateAnswer() - .withGasLimit(new GasLimit(543210)) - .check(); + .withGasLimit(new GasLimit(543210)); - assert.equal(interaction.getContract().getAddress(), dummyAddress); + assert.equal(contract.getAddress(), dummyAddress); assert.deepEqual(interaction.getFunction(), new ContractFunction("getUltimateAnswer")); assert.lengthOf(interaction.getArguments(), 0); assert.deepEqual(interaction.getGasLimit(), new GasLimit(543210)); - provider.mockQueryResponseOnFunction( + provider.mockQueryContractOnFunction( "getUltimateAnswer", new QueryResponse({ returnData: ["Kg=="], returnCode: ReturnCode.Ok }) ); // Query - let { values: queryValues, firstValue: queryAnwser, returnCode: queryCode } = await runner.runQuery( + let { values: queryValues, firstValue: queryAnwser, returnCode: queryCode } = await controller.query( interaction ); assert.lengthOf(queryValues, 1); - assert.deepEqual(queryAnwser.valueOf(), new BigNumber(42)); + assert.deepEqual(queryAnwser!.valueOf(), new BigNumber(42)); assert.isTrue(queryCode.equals(ReturnCode.Ok)); // Execute, do not wait for execution @@ -161,22 +158,14 @@ describe("test smart contract interactor", function() { ); // Execute, and wait for execution - let [, transactionOnNetwork ] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: @6f6b@2bs - new MarkNotarized(), - ]), - (async () => { - let transaction = interaction.withNonce(new Nonce(2)).buildTransaction(); - await alice.signer.sign(transaction); - return await runner.run(transaction); - })() - ]); - - // TODO: assert.lengthOf(executionValues, 1); - // TODO: assert.deepEqual(executionAnswer.valueOf(), new BigNumber(43)); - // TODO: assert.isTrue(executionCode.equals(ReturnCode.Ok)); + transaction = interaction.withNonce(new Nonce(2)).buildTransaction(); + await alice.signer.sign(transaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@2bs" })]) + let { bundle } = await controller.execute(interaction, transaction); + + assert.lengthOf(bundle.values, 1); + assert.deepEqual(bundle.firstValue!.valueOf(), new BigNumber(43)); + assert.isTrue(bundle.returnCode.equals(ReturnCode.Ok)); }); it("should interact with 'counter'", async function() { @@ -185,41 +174,28 @@ describe("test smart contract interactor", function() { let abiRegistry = await loadAbiRegistry(["src/testdata/counter.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["counter"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); + let controller = new DefaultInteractionController(abi, provider); let getInteraction = contract.methods.get(); - let incrementInteraction = (contract.methods.increment()) - .withGasLimit(new GasLimit(543210)) - .check(); - let decrementInteraction = (contract.methods.decrement()) - .withGasLimit(new GasLimit(987654)) - .check(); + let incrementInteraction = (contract.methods.increment()).withGasLimit(new GasLimit(543210)); + let decrementInteraction = (contract.methods.decrement()).withGasLimit(new GasLimit(987654)); // For "get()", return fake 7 - provider.mockQueryResponseOnFunction( + provider.mockQueryContractOnFunction( "get", new QueryResponse({ returnData: ["Bw=="], returnCode: ReturnCode.Ok }) ); // Query "get()" - let { firstValue: counterValue } = await runner.runQuery(getInteraction); - - assert.deepEqual(counterValue.valueOf(), new BigNumber(7)); - - // Increment, wait for execution. Return fake 8 - let [, transactionOnNetworkForIncrement ] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: @6f6b@08 - new MarkNotarized(), - ]), - (async () => { - let transaction = incrementInteraction.withNonce(new Nonce(14)).buildTransaction(); - await alice.signer.sign(transaction); - return await runner.run(transaction); - })() - ]); - - // TODO: assert.deepEqual(valueAfterIncrement.valueOf(), new BigNumber(8)); + let { firstValue: counterValue } = await controller.query(getInteraction); + + assert.deepEqual(counterValue!.valueOf(), new BigNumber(7)); + + let incrementTransaction = incrementInteraction.withNonce(new Nonce(14)).buildTransaction(); + await alice.signer.sign(incrementTransaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@08" })]) + let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction); + assert.deepEqual(valueAfterIncrement!.valueOf(), new BigNumber(8)); // Decrement three times (simulate three parallel broadcasts). Wait for execution of the latter (third transaction). Return fake "5". // Decrement #1 @@ -231,20 +207,12 @@ describe("test smart contract interactor", function() { await alice.signer.sign(decrementTransaction); decrementTransaction.send(provider); // Decrement #3 - let [, transactionOnNetworkForDecrement ] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: @6f6b@05 - new MarkNotarized(), - ]), - (async () => { - let decrementTransaction = decrementInteraction.withNonce(new Nonce(17)).buildTransaction(); - await alice.signer.sign(decrementTransaction); - return await runner.run(decrementTransaction); - })() - ]); - - // TODO: assert.deepEqual(valueAfterDecrement.valueOf(), new BigNumber(5)); + + decrementTransaction = decrementInteraction.withNonce(new Nonce(17)).buildTransaction(); + await alice.signer.sign(decrementTransaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@05" })]) + let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); + assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(5)); }); it("should interact with 'lottery_egld'", async function() { @@ -253,6 +221,7 @@ describe("test smart contract interactor", function() { let abiRegistry = await loadAbiRegistry(["src/testdata/lottery_egld.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); + let controller = new DefaultInteractionController(abi, provider); let startInteraction = ( contract.methods @@ -266,99 +235,56 @@ describe("test smart contract interactor", function() { OptionValue.newMissing(), ]) .withGasLimit(new GasLimit(5000000)) - .check() ); - let lotteryStatusInteraction = ( + let statusInteraction = ( contract.methods.status([BytesValue.fromUTF8("lucky")]).withGasLimit(new GasLimit(5000000)) ); - let getLotteryInfoInteraction = ( + let lotteryInfoInteraction = ( contract.methods.lotteryInfo([BytesValue.fromUTF8("lucky")]).withGasLimit(new GasLimit(5000000)) ); // start() - let [, transactionOnNetworkForStart ] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: @6f6b - new MarkNotarized(), - ]), - (async () => { - let transaction = startInteraction.withNonce(new Nonce(14)).buildTransaction(); - await alice.signer.sign(transaction); - return await runner.run(transaction); - })() - ]); - - assert.equal( - startInteraction - .buildTransaction() - .getData() - .toString(), - "start@6c75636b79@0de0b6b3a7640000@@@0100000001@@" - ); - // TODO: assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); - // TODO: assert.lengthOf(startReturnvalues, 0); - - // lotteryExists() (this is a view function, but for the sake of the test, we'll execute it) - let [, transactionOnNetworkForLotteryExists ] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: @6f6b@01 - new MarkNotarized(), - ]), - (async () => { - let transaction = lotteryStatusInteraction.withNonce(new Nonce(15)).buildTransaction(); - await alice.signer.sign(transaction); - return await runner.run(transaction); - })() - ]); - - assert.equal( - lotteryStatusInteraction - .buildTransaction() - .getData() - .toString(), - "status@6c75636b79" - ); - // TODO: assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); - // TODO: assert.lengthOf(statusReturnvalues, 1); - // TODO: assert.deepEqual(statusFirstValue.valueOf(), { name: "Running", fields: [] }); + let startTransaction = startInteraction.withNonce(new Nonce(14)).buildTransaction(); + await alice.signer.sign(startTransaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b" })]) + let { bundle: { returnCode: startReturnCode, values: startReturnValues } } = await controller.execute(startInteraction, startTransaction); + + assert.equal(startTransaction.getData().toString(), "start@6c75636b79@0de0b6b3a7640000@@@0100000001@@"); + assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); + assert.lengthOf(startReturnValues, 0); + + // status() (this is a view function, but for the sake of the test, we'll execute it) + let statusTransaction = statusInteraction.withNonce(new Nonce(15)).buildTransaction(); + await alice.signer.sign(statusTransaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@01" })]) + let { bundle: { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } } = await controller.execute(statusInteraction, statusTransaction); + + assert.equal(statusTransaction.getData().toString(),"status@6c75636b79"); + assert.isTrue(statusReturnCode.equals(ReturnCode.Ok)); + assert.lengthOf(statusReturnValues, 1); + assert.deepEqual(statusFirstValue!.valueOf(), { name: "Running", fields: [] }); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let [, transactionOnNetworkForGetInfo] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: "@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000" - new MarkNotarized(), - ]), - (async () => { - let transaction = getLotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); - await alice.signer.sign(transaction); - return await runner.run(transaction); - })() - ]); - - assert.equal( - getLotteryInfoInteraction - .buildTransaction() - .getData() - .toString(), - "lotteryInfo@6c75636b79" - ); - // TODO: assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); - // TODO: assert.lengthOf(infoReturnvalues, 1); - - // TODO: assert.deepEqual(infoFirstValue.valueOf(), { - // ticket_price: new BigNumber("1000000000000000000"), - // tickets_left: new BigNumber(800), - // deadline: new BigNumber("1611835398"), - // max_entries_per_user: new BigNumber(1), - // prize_distribution: Buffer.from([0x64]), - // whitelist: [], - // current_ticket_number: new BigNumber(0), - // prize_pool: new BigNumber("0"), - // }); + let lotteryInfoTransaction = lotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); + await alice.signer.sign(lotteryInfoTransaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000" })]) + let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue} } = await controller.execute(lotteryInfoInteraction, lotteryInfoTransaction); + + assert.equal(lotteryInfoTransaction.getData().toString(), "lotteryInfo@6c75636b79"); + assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); + assert.lengthOf(infoReturnValues, 1); + + assert.deepEqual(infoFirstValue!.valueOf(), { + ticket_price: new BigNumber("1000000000000000000"), + tickets_left: new BigNumber(800), + deadline: new BigNumber("1611835398"), + max_entries_per_user: new BigNumber(1), + prize_distribution: Buffer.from([0x64]), + whitelist: [], + current_ticket_number: new BigNumber(0), + prize_pool: new BigNumber("0"), + }); }); }); diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index fb7861c0..2102afe1 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -2,17 +2,22 @@ import { Balance } from "../balance"; import { GasLimit } from "../networkParams"; import { Transaction } from "../transaction"; import { Query } from "./query"; -import { QueryResponse } from "./queryResponse"; import { ContractFunction } from "./function"; import { Address } from "../address"; -import { SmartContract } from "./smartContract"; -import { AddressValue, BigUIntValue, BytesValue, EndpointDefinition, TypedValue, U64Value, U8Value } from "./typesystem"; +import { AddressValue, BigUIntValue, BytesValue, TypedValue, U64Value, U8Value } from "./typesystem"; import { Nonce } from "../nonce"; -import { QueryResponseBundle } from "./interface"; import { NetworkConfig } from "../networkConfig"; import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; -import { InteractionChecker } from "./interactionChecker"; import { Account } from "../account"; +import { CallArguments } from "./interface"; + +/** + * 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; +} /** * Interactions can be seen as mutable transaction & query builders. @@ -21,8 +26,7 @@ import { Account } from "../account"; * the execution outcome for the objects they've built. */ export class Interaction { - private readonly checker: InteractionChecker; - private readonly contract: SmartContract; + private readonly contract: ISmartContractWithinInteraction; private readonly function: ContractFunction; private readonly args: TypedValue[]; private readonly receiver?: Address; @@ -39,20 +43,19 @@ export class Interaction { private tokenTransfersSender: Address = new Address(); constructor( - contract: SmartContract, + contract: ISmartContractWithinInteraction, func: ContractFunction, args: TypedValue[], receiver?: Address, ) { - this.checker = new InteractionChecker(); this.contract = contract; this.function = func; this.args = args; this.receiver = receiver; this.tokenTransfers = new TokenTransfersWithinInteraction([], this); } - - getContract(): SmartContract { + + getContract(): ISmartContractWithinInteraction { return this.contract; } @@ -122,25 +125,6 @@ export class Interaction { }); } - /** - * Interprets the raw outcome of a Smart Contract query. - * The outcome is structured such that it allows quick access to each level of detail. - */ - interpretQueryResponse(queryResponse: QueryResponse): QueryResponseBundle { - let endpoint = this.getEndpoint(); - queryResponse.setEndpointDefinition(endpoint); - - let values = queryResponse.outputTyped(); - let returnCode = queryResponse.returnCode; - - return { - queryResponse: queryResponse, - values: values, - firstValue: values[0], - returnCode: returnCode - }; - } - withValue(value: Balance): Interaction { this.value = value; return this; @@ -200,22 +184,6 @@ export class Interaction { this.querent = querent; return this; } - - /** - * Checks the prepared interaction against the ABI endpoint definition. - * This function throws if type mismatches (provided vs. ABI) are encountered. - * - * When the ABI is available, it is always recommended to call {@link check} before - * {@link buildTransaction}. - */ - check(): Interaction { - this.checker.checkInteraction(this); - return this; - } - - getEndpoint(): EndpointDefinition { - return this.getContract().getAbi().getEndpoint(this.getFunction()); - } } class TokenTransfersWithinInteraction { diff --git a/src/smartcontracts/interactionChecker.spec.ts b/src/smartcontracts/interactionChecker.spec.ts index 8931cb18..f964b56a 100644 --- a/src/smartcontracts/interactionChecker.spec.ts +++ b/src/smartcontracts/interactionChecker.spec.ts @@ -19,17 +19,18 @@ describe("integration tests: test checker within interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["answer"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); + let endpoint = abi.getEndpoint("getUltimateAnswer"); // Send value to non-payable assert.throw(() => { let interaction = (contract.methods.getUltimateAnswer()).withValue(Balance.egld(1)); - checker.checkInteraction(interaction); + checker.checkInteraction(interaction, endpoint); }, errors.ErrContractInteraction, "cannot send EGLD value to non-payable"); // Bad arguments assert.throw(() => { let interaction = (contract.methods.getUltimateAnswer([BytesValue.fromHex("abba")])); - checker.checkInteraction(interaction); + checker.checkInteraction(interaction, endpoint); }, errors.ErrContractInteraction, "bad arguments, expected: 0, got: 1"); }); @@ -37,6 +38,7 @@ describe("integration tests: test checker within interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/lottery_egld.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); + let endpoint = abi.getEndpoint("start"); // Bad number of arguments assert.throw(() => { @@ -45,7 +47,7 @@ describe("integration tests: test checker within interactor", function () { new BigUIntValue(Balance.egld(1).valueOf()), OptionValue.newMissing() ]); - checker.checkInteraction(interaction); + checker.checkInteraction(interaction, endpoint); }, errors.ErrContractInteraction, "bad arguments, expected: 7, got: 3"); // Bad types (U64 instead of U32) @@ -59,7 +61,7 @@ describe("integration tests: test checker within interactor", function () { OptionValue.newMissing(), OptionValue.newMissing(), ]); - checker.checkInteraction(interaction); + checker.checkInteraction(interaction, endpoint); }, errors.ErrContractInteraction, "type mismatch at index 4, expected: Option, got: Option"); }); }); diff --git a/src/smartcontracts/interactionChecker.ts b/src/smartcontracts/interactionChecker.ts index f38e6e25..7ca78b91 100644 --- a/src/smartcontracts/interactionChecker.ts +++ b/src/smartcontracts/interactionChecker.ts @@ -1,6 +1,7 @@ import * as errors from "../errors"; import { EndpointDefinition } from "./typesystem"; import { Interaction } from "./interaction"; +import { IInteractionChecker } from "./interface"; /** * An interaction checker that aims to be as strict as possible. @@ -9,10 +10,8 @@ import { Interaction } from "./interaction"; * - errors related to calling "non-payable" functions with some value provided * - gas estimation errors (not yet implemented) */ -export class InteractionChecker { - checkInteraction(interaction: Interaction): void { - let definition = interaction.getEndpoint(); - +export class InteractionChecker implements IInteractionChecker { + checkInteraction(interaction: Interaction, definition: EndpointDefinition): void { this.checkPayable(interaction, definition); this.checkArguments(interaction, definition); } @@ -51,3 +50,7 @@ export class InteractionChecker { } } } + +export class NullInteractionChecker implements IInteractionChecker { + checkInteraction(_interaction: Interaction, _definition: EndpointDefinition): void { } +} diff --git a/src/smartcontracts/interactionController.ts b/src/smartcontracts/interactionController.ts new file mode 100644 index 00000000..fe5e8b37 --- /dev/null +++ b/src/smartcontracts/interactionController.ts @@ -0,0 +1,85 @@ +import { IProvider } from "../interface"; +import { Interaction } from "./interaction"; +import { Transaction } from "../transaction"; +import { TransactionOnNetwork } from "../transactionOnNetwork"; +import { ContractOutcomeBundle, IInteractionChecker, IResultsParser } from "./interface"; +import { ContractFunction } from "./function"; +import { ResultsParser } from "./resultsParser"; +import { InteractionChecker, NullInteractionChecker } from "./interactionChecker"; +import { EndpointDefinition } from "./typesystem"; + +/** + * Internal interface: the smart contract ABI, as seen from the perspective of an {@link InteractionController}. + */ +interface ISmartContractAbi { + getEndpoint(func: ContractFunction): EndpointDefinition; +} + +/** + * An interaction controller, suitable for frontends and dApp, + * where signing is performed by means of an external wallet provider. + */ +export class InteractionController { + private readonly abi: ISmartContractAbi; + private readonly checker: IInteractionChecker; + private readonly parser: IResultsParser; + private readonly provider: IProvider; + + constructor( + abi: ISmartContractAbi, + checker: IInteractionChecker, + parser: IResultsParser, + provider: IProvider, + ) { + this.abi = abi; + this.checker = checker; + this.parser = parser; + this.provider = provider; + } + + /** + * Broadcasts an alredy-signed interaction transaction, and also waits for its execution on the Network. + * + * @param interaction The interaction used to build the {@link signedInteractionTransaction} + * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand + */ + async execute(interaction: Interaction, signedInteractionTransaction: Transaction): Promise<{ transaction: TransactionOnNetwork, bundle: ContractOutcomeBundle }> { + let endpoint = this.getEndpoint(interaction); + + this.checker.checkInteraction(interaction, endpoint); + + await signedInteractionTransaction.send(this.provider); + await signedInteractionTransaction.awaitExecuted(this.provider); + let transactionOnNetwork = await signedInteractionTransaction.getAsOnNetwork(this.provider); + let outcomeBundle = this.parser.parseOutcome(transactionOnNetwork, endpoint); + return { transaction: transactionOnNetwork, bundle: outcomeBundle }; + } + + async query(interaction: Interaction): Promise { + let endpoint = this.getEndpoint(interaction); + + this.checker.checkInteraction(interaction, endpoint); + + let query = interaction.buildQuery(); + let queryResponse = await this.provider.queryContract(query); + let outcomeBundle = this.parser.parseQueryResponse(queryResponse, endpoint); + return outcomeBundle; + } + + private getEndpoint(interaction: Interaction) { + let func = interaction.getFunction(); + return this.abi.getEndpoint(func); + } +} + +export class DefaultInteractionController extends InteractionController { + constructor(abi: ISmartContractAbi, provider: IProvider) { + super(abi, new InteractionChecker(), new ResultsParser(), provider); + } +} + +export class NoCheckInteractionController extends InteractionController { + constructor(abi: ISmartContractAbi, provider: IProvider) { + super(abi, new NullInteractionChecker(), new ResultsParser(), provider); + } +} diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts new file mode 100644 index 00000000..2ac7eb92 --- /dev/null +++ b/src/smartcontracts/resultsParser.ts @@ -0,0 +1,57 @@ +import { ErrInvariantFailed } from "../errors"; +import { TransactionOnNetwork } from "../transactionOnNetwork"; +import { ArgSerializer } from "./argSerializer"; +import { ContractOutcomeBundle, IResultsParser } from "./interface"; +import { QueryResponse } from "./queryResponse"; +import { ReturnCode } from "./returnCode"; +import { EndpointDefinition } from "./typesystem"; + +export class ResultsParser implements IResultsParser { + parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): ContractOutcomeBundle { + let parts = queryResponse.getReturnDataParts(); + let values = new ArgSerializer().buffersToValues(parts, endpoint.output); + + return { + returnCode: queryResponse.returnCode, + returnMessage: queryResponse.returnMessage, + values: values, + firstValue: values[0], + secondValue: values[1], + thirdValue: values[2] + }; + } + + parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): ContractOutcomeBundle { + let resultItems = transaction.results.getAll(); + // TODO: Fix! The condition below IS NOT CORRECT (not sufficient). + let resultItemWithReturnData = resultItems.find(item => item.nonce.valueOf() != 0); + if (!resultItemWithReturnData) { + throw new ErrInvariantFailed("SCR with return data not found"); + } + + let parts = resultItemWithReturnData.getDataParts(); + let emptyReturnPart = parts[0] || Buffer.from([]); + let returnCodePart = parts[1] || Buffer.from([]); + let returnDataParts = parts.slice(2); + + if (emptyReturnPart.length != 0) { + throw new ErrInvariantFailed("Cannot parse contract return data. No leading empty part."); + } + + if (returnCodePart.length == 0) { + throw new ErrInvariantFailed("Cannot parse contract return code."); + } + + let returnCode = ReturnCode.fromBuffer(returnCodePart); + let values = new ArgSerializer().buffersToValues(returnDataParts, endpoint.output); + + return { + returnCode: returnCode, + returnMessage: returnCode.toString(), + values: values, + firstValue: values[0], + secondValue: values[1], + thirdValue: values[2] + }; + } +} From d684b62ab877a15ee2ff466feb668c7e6ac295b3 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 11:22:37 +0200 Subject: [PATCH 08/37] Fix upon change, work in progress. --- src/smartcontracts/wrapper/contractLogger.ts | 2 +- src/smartcontracts/wrapper/contractWrapper.ts | 20 +++++------ ...edSCRs.ts => deprecatedContractResults.ts} | 33 ++++++++++++++++--- src/smartcontracts/wrapper/result.ts | 10 ++---- 4 files changed, 42 insertions(+), 23 deletions(-) rename src/smartcontracts/wrapper/{deprecatedSCRs.ts => deprecatedContractResults.ts} (82%) diff --git a/src/smartcontracts/wrapper/contractLogger.ts b/src/smartcontracts/wrapper/contractLogger.ts index a597dd2b..ea7a6302 100644 --- a/src/smartcontracts/wrapper/contractLogger.ts +++ b/src/smartcontracts/wrapper/contractLogger.ts @@ -4,7 +4,7 @@ import { Transaction } from "../../transaction"; import { Query } from "../query"; import { QueryResponse } from "../queryResponse"; import { SmartContractResults } from "../smartContractResults"; -import { findImmediateResult, findResultingCalls, TypedResult } from "./deprecatedSCRs"; +import { findImmediateResult, findResultingCalls, TypedResult } from "./deprecatedContractResults"; /** * Provides a simple interface in order to easily call or query the smart contract's methods. diff --git a/src/smartcontracts/wrapper/contractWrapper.ts b/src/smartcontracts/wrapper/contractWrapper.ts index bdca01a1..75fa10a0 100644 --- a/src/smartcontracts/wrapper/contractWrapper.ts +++ b/src/smartcontracts/wrapper/contractWrapper.ts @@ -21,7 +21,7 @@ import { Interaction } from "../interaction"; import { Err, ErrContract, ErrInvalidArgument } from "../../errors"; import { Egld } from "../../balanceBuilder"; import { Balance } from "../../balance"; -import { ExecutionResultsBundle, findImmediateResult, interpretExecutionResults } from "./deprecatedSCRs"; +import { ExecutionResultsBundle, findImmediateResult, interpretExecutionResults, interpretQueryResponse } from "./deprecatedContractResults"; import { Result } from "./result"; /** @@ -110,7 +110,7 @@ export class ContractWrapper extends ChainSendContext { let transactionOnNetwork = await this.processTransaction(transaction); - let smartContractResults = transactionOnNetwork.getSmartContractResults(); + let smartContractResults = transactionOnNetwork.results; let immediateResult = findImmediateResult(smartContractResults)!; immediateResult.assertSuccess(); let logger = this.context.getLogger(); @@ -140,7 +140,7 @@ export class ContractWrapper extends ChainSendContext { } let response = await provider.queryContract(query); console.log("got response...", response); - let queryResponseBundle = interaction.interpretQueryResponse(response); + let queryResponseBundle = interpretQueryResponse(endpoint, response); let result = Result.unpackOutput(queryResponseBundle.queryResponse); logger?.queryComplete(result, response); @@ -149,22 +149,22 @@ export class ContractWrapper extends ChainSendContext { async handleCall(endpoint: EndpointDefinition, ...args: any[]): Promise { let { transaction, interaction } = this.buildTransactionAndInteraction(endpoint, args); - let { result } = await this.processTransactionAndInterpretResults({ transaction, interaction }); + let { result } = await this.processTransactionAndInterpretResults({ endpoint, transaction }); return result; } async handleResults(endpoint: EndpointDefinition, ...args: any[]): Promise { let { transaction, interaction } = this.buildTransactionAndInteraction(endpoint, args); - let { executionResultsBundle } = await this.processTransactionAndInterpretResults({ transaction, interaction }); + let { executionResultsBundle } = await this.processTransactionAndInterpretResults({ endpoint, transaction }); return executionResultsBundle; } - async processTransactionAndInterpretResults({ transaction, interaction }: { - transaction: Transaction, - interaction: Interaction + async processTransactionAndInterpretResults({ endpoint, transaction }: { + endpoint: EndpointDefinition, + transaction: Transaction }): Promise<{ executionResultsBundle: ExecutionResultsBundle, result: any }> { let transactionOnNetwork = await this.processTransaction(transaction); - let executionResultsBundle = interpretExecutionResults(interaction.getEndpoint(), transactionOnNetwork); + let executionResultsBundle = interpretExecutionResults(endpoint, transactionOnNetwork); let { smartContractResults, immediateResult } = executionResultsBundle; let result = immediateResult?.unpackOutput(); let logger = this.context.getLogger(); @@ -192,7 +192,7 @@ export class ContractWrapper extends ChainSendContext { let transactionOnNetwork = await transaction.getAsOnNetwork(provider, true, false, true); if (transaction.getStatus().isFailed()) { // TODO: extract the error messages - //let results = transactionOnNetwork.getSmartContractResults().getAllResults(); + //let results = transactionOnNetwork.results.getAllResults(); //let messages = results.map((result) => console.log(result)); throw new ErrContract(`Transaction status failed: [${transaction.getStatus().toString()}].`);// Return messages:\n${messages}`); } diff --git a/src/smartcontracts/wrapper/deprecatedSCRs.ts b/src/smartcontracts/wrapper/deprecatedContractResults.ts similarity index 82% rename from src/smartcontracts/wrapper/deprecatedSCRs.ts rename to src/smartcontracts/wrapper/deprecatedContractResults.ts index f7e18a40..30cfff2f 100644 --- a/src/smartcontracts/wrapper/deprecatedSCRs.ts +++ b/src/smartcontracts/wrapper/deprecatedContractResults.ts @@ -1,4 +1,6 @@ import { TransactionOnNetwork } from "../../transactionOnNetwork"; +import { ArgSerializer } from "../argSerializer"; +import { QueryResponse } from "../queryResponse"; import { ReturnCode } from "../returnCode"; import { SmartContractResultItem, SmartContractResults } from "../smartContractResults"; import { EndpointDefinition, TypedValue } from "../typesystem"; @@ -9,7 +11,7 @@ import { Result } from "./result"; * The SCRs are more alike a graph. */ export function interpretExecutionResults(endpoint: EndpointDefinition, transactionOnNetwork: TransactionOnNetwork): ExecutionResultsBundle { - let smartContractResults = transactionOnNetwork.getSmartContractResults(); + let smartContractResults = transactionOnNetwork.results; let immediateResult = findImmediateResult(smartContractResults)!; let resultingCalls = findResultingCalls(smartContractResults); @@ -29,6 +31,22 @@ export function interpretExecutionResults(endpoint: EndpointDefinition, transact }; } +/** + * @deprecated + */ +export function interpretQueryResponse(endpoint: EndpointDefinition, queryResponse: QueryResponse): QueryResponseBundle { + let buffers = queryResponse.getReturnDataParts(); + let values = new ArgSerializer().buffersToValues(buffers, endpoint.output); + let returnCode = queryResponse.returnCode; + + return { + queryResponse: queryResponse, + values: values, + firstValue: values[0], + returnCode: returnCode + }; +} + /** * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. * The SCRs are more alike a graph. @@ -43,6 +61,13 @@ export interface ExecutionResultsBundle { returnCode: ReturnCode; } +export interface QueryResponseBundle { + queryResponse: QueryResponse; + firstValue: TypedValue; + values: TypedValue[]; + returnCode: ReturnCode; +} + /** * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. * The SCRs are more alike a graph. @@ -97,7 +122,7 @@ export class TypedResult extends SmartContractResultItem implements Result.IResu } getReturnCode(): ReturnCode { - let tokens = this.getDataTokens(); + let tokens = this.getDataParts(); if (tokens.length < 2) { return ReturnCode.None; } @@ -109,7 +134,7 @@ export class TypedResult extends SmartContractResultItem implements Result.IResu this.assertSuccess(); // Skip the first 2 SCRs (eg. the @6f6b from @6f6b@2b). - return this.getDataTokens().slice(2); + return this.getDataParts().slice(2); } setEndpointDefinition(endpointDefinition: EndpointDefinition) { @@ -131,4 +156,4 @@ export class TypedResult extends SmartContractResultItem implements Result.IResu unpackOutput(): any { return Result.unpackOutput(this); } -} \ No newline at end of file +} diff --git a/src/smartcontracts/wrapper/result.ts b/src/smartcontracts/wrapper/result.ts index 9f601a97..646e1305 100644 --- a/src/smartcontracts/wrapper/result.ts +++ b/src/smartcontracts/wrapper/result.ts @@ -1,5 +1,4 @@ import { ErrContract } from "../../errors"; -import { guardValueIsSet } from "../../utils"; import { ArgSerializer } from "../argSerializer"; import { ReturnCode } from "../returnCode"; import { EndpointDefinition, TypedValue } from "../typesystem"; @@ -7,8 +6,6 @@ import { EndpointDefinition, TypedValue } from "../typesystem"; export namespace Result { export interface IResult { - setEndpointDefinition(endpointDefinition: EndpointDefinition): void; - getEndpointDefinition(): EndpointDefinition | undefined; getReturnCode(): ReturnCode; getReturnMessage(): string; isSuccess(): boolean; @@ -29,14 +26,11 @@ export namespace Result { throw new ErrContract(`${result.getReturnCode()}: ${result.getReturnMessage()}`); } - export function outputTyped(result: IResult) { + export function outputTyped(endpointDefinition: EndpointDefinition, result: IResult) { result.assertSuccess(); - let endpointDefinition = result.getEndpointDefinition(); - guardValueIsSet("endpointDefinition", endpointDefinition); - let buffers = result.outputUntyped(); - let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition!.output); + let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition.output); return values; } From ca79b9760b10d9cc6b6876e81daff6fb9b286e2c Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 12:03:29 +0200 Subject: [PATCH 09/37] Fix after refactor, fix tests. --- src/smartcontracts/interaction.spec.ts | 12 +++--- .../wrapper/contractWrapper.spec.ts | 15 ++------ src/smartcontracts/wrapper/contractWrapper.ts | 7 ++-- .../wrapper/deprecatedContractResults.ts | 37 +------------------ src/smartcontracts/wrapper/result.ts | 26 +++++++------ src/testutils/mockProvider.ts | 8 ++-- 6 files changed, 34 insertions(+), 71 deletions(-) diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index e25c3724..9e520436 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -160,7 +160,7 @@ describe("test smart contract interactor", function() { // Execute, and wait for execution transaction = interaction.withNonce(new Nonce(2)).buildTransaction(); await alice.signer.sign(transaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@2bs" })]) + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@2bs"); let { bundle } = await controller.execute(interaction, transaction); assert.lengthOf(bundle.values, 1); @@ -193,7 +193,7 @@ describe("test smart contract interactor", function() { let incrementTransaction = incrementInteraction.withNonce(new Nonce(14)).buildTransaction(); await alice.signer.sign(incrementTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@08" })]) + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@08"); let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction); assert.deepEqual(valueAfterIncrement!.valueOf(), new BigNumber(8)); @@ -210,7 +210,7 @@ describe("test smart contract interactor", function() { decrementTransaction = decrementInteraction.withNonce(new Nonce(17)).buildTransaction(); await alice.signer.sign(decrementTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@05" })]) + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@05"); let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(5)); }); @@ -248,7 +248,7 @@ describe("test smart contract interactor", function() { // start() let startTransaction = startInteraction.withNonce(new Nonce(14)).buildTransaction(); await alice.signer.sign(startTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b" })]) + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b"); let { bundle: { returnCode: startReturnCode, values: startReturnValues } } = await controller.execute(startInteraction, startTransaction); assert.equal(startTransaction.getData().toString(), "start@6c75636b79@0de0b6b3a7640000@@@0100000001@@"); @@ -258,7 +258,7 @@ describe("test smart contract interactor", function() { // status() (this is a view function, but for the sake of the test, we'll execute it) let statusTransaction = statusInteraction.withNonce(new Nonce(15)).buildTransaction(); await alice.signer.sign(statusTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@01" })]) + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@01"); let { bundle: { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } } = await controller.execute(statusInteraction, statusTransaction); assert.equal(statusTransaction.getData().toString(),"status@6c75636b79"); @@ -269,7 +269,7 @@ describe("test smart contract interactor", function() { // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) let lotteryInfoTransaction = lotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); await alice.signer.sign(lotteryInfoTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithContractResults([new SmartContractResultItem({ data: "@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000" })]) + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000"); let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue} } = await controller.execute(lotteryInfoInteraction, lotteryInfoTransaction); assert.equal(lotteryInfoTransaction.getData().toString(), "lotteryInfo@6c75636b79"); diff --git a/src/smartcontracts/wrapper/contractWrapper.spec.ts b/src/smartcontracts/wrapper/contractWrapper.spec.ts index 84f5d2fc..e3f5ea49 100644 --- a/src/smartcontracts/wrapper/contractWrapper.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.spec.ts @@ -1,5 +1,4 @@ import { - MarkNotarized, MockProvider, setupUnitTestWatcherTimeouts, TestWallet, @@ -7,7 +6,6 @@ import { import { Address } from "../../address"; import { assert } from "chai"; import { QueryResponse } from "../queryResponse"; -import { TransactionStatus } from "../../transaction"; import { ReturnCode } from "../returnCode"; import BigNumber from "bignumber.js"; import { SystemWrapper } from "./systemWrapper"; @@ -108,14 +106,7 @@ function mockQuery(provider: MockProvider, functionName: string, mockedResult: s ); } -async function mockCall(provider: MockProvider, _mockedResult: string, promise: Promise) { - let [, value] = await Promise.all([ - provider.mockNextTransactionTimeline([ - new TransactionStatus("executed"), - // TODO: Add SCRs (_mockedResult) - new MarkNotarized(), - ]), - promise, - ]); - return value; +async function mockCall(provider: MockProvider, mockedResult: string, promise: Promise) { + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult(mockedResult); + return await promise; } diff --git a/src/smartcontracts/wrapper/contractWrapper.ts b/src/smartcontracts/wrapper/contractWrapper.ts index 75fa10a0..cd101a60 100644 --- a/src/smartcontracts/wrapper/contractWrapper.ts +++ b/src/smartcontracts/wrapper/contractWrapper.ts @@ -21,7 +21,7 @@ import { Interaction } from "../interaction"; import { Err, ErrContract, ErrInvalidArgument } from "../../errors"; import { Egld } from "../../balanceBuilder"; import { Balance } from "../../balance"; -import { ExecutionResultsBundle, findImmediateResult, interpretExecutionResults, interpretQueryResponse } from "./deprecatedContractResults"; +import { ExecutionResultsBundle, findImmediateResult, interpretExecutionResults } from "./deprecatedContractResults"; import { Result } from "./result"; /** @@ -140,8 +140,7 @@ export class ContractWrapper extends ChainSendContext { } let response = await provider.queryContract(query); console.log("got response...", response); - let queryResponseBundle = interpretQueryResponse(endpoint, response); - let result = Result.unpackOutput(queryResponseBundle.queryResponse); + let result = Result.unpackQueryOutput(endpoint, response); logger?.queryComplete(result, response); return result; @@ -166,7 +165,7 @@ export class ContractWrapper extends ChainSendContext { let transactionOnNetwork = await this.processTransaction(transaction); let executionResultsBundle = interpretExecutionResults(endpoint, transactionOnNetwork); let { smartContractResults, immediateResult } = executionResultsBundle; - let result = immediateResult?.unpackOutput(); + let result = Result.unpackExecutionOutput(endpoint, immediateResult); let logger = this.context.getLogger(); logger?.transactionComplete(result, immediateResult?.data, transaction, smartContractResults); return { executionResultsBundle, result }; diff --git a/src/smartcontracts/wrapper/deprecatedContractResults.ts b/src/smartcontracts/wrapper/deprecatedContractResults.ts index 30cfff2f..f91c1b11 100644 --- a/src/smartcontracts/wrapper/deprecatedContractResults.ts +++ b/src/smartcontracts/wrapper/deprecatedContractResults.ts @@ -15,9 +15,8 @@ export function interpretExecutionResults(endpoint: EndpointDefinition, transact let immediateResult = findImmediateResult(smartContractResults)!; let resultingCalls = findResultingCalls(smartContractResults); - immediateResult.setEndpointDefinition(endpoint); - - let values = immediateResult.outputTyped(); + let buffers = immediateResult.outputUntyped(); + let values = new ArgSerializer().buffersToValues(buffers, endpoint.output); let returnCode = immediateResult.getReturnCode(); return { @@ -31,22 +30,6 @@ export function interpretExecutionResults(endpoint: EndpointDefinition, transact }; } -/** - * @deprecated - */ -export function interpretQueryResponse(endpoint: EndpointDefinition, queryResponse: QueryResponse): QueryResponseBundle { - let buffers = queryResponse.getReturnDataParts(); - let values = new ArgSerializer().buffersToValues(buffers, endpoint.output); - let returnCode = queryResponse.returnCode; - - return { - queryResponse: queryResponse, - values: values, - firstValue: values[0], - returnCode: returnCode - }; -} - /** * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. * The SCRs are more alike a graph. @@ -137,23 +120,7 @@ export class TypedResult extends SmartContractResultItem implements Result.IResu return this.getDataParts().slice(2); } - setEndpointDefinition(endpointDefinition: EndpointDefinition) { - this.endpointDefinition = endpointDefinition; - } - - getEndpointDefinition(): EndpointDefinition | undefined { - return this.endpointDefinition; - } - getReturnMessage(): string { return "TODO: the return message isn't available on SmartContractResultItem (not provided by the API)"; } - - outputTyped(): TypedValue[] { - return Result.outputTyped(this); - } - - unpackOutput(): any { - return Result.unpackOutput(this); - } } diff --git a/src/smartcontracts/wrapper/result.ts b/src/smartcontracts/wrapper/result.ts index 646e1305..391cc66a 100644 --- a/src/smartcontracts/wrapper/result.ts +++ b/src/smartcontracts/wrapper/result.ts @@ -1,7 +1,9 @@ import { ErrContract } from "../../errors"; import { ArgSerializer } from "../argSerializer"; +import { QueryResponse } from "../queryResponse"; import { ReturnCode } from "../returnCode"; -import { EndpointDefinition, TypedValue } from "../typesystem"; +import { EndpointDefinition } from "../typesystem"; +import { TypedResult } from "./deprecatedContractResults"; export namespace Result { @@ -10,8 +12,6 @@ export namespace Result { getReturnMessage(): string; isSuccess(): boolean; assertSuccess(): void; - outputUntyped(): Buffer[]; - outputTyped(): TypedValue[]; } export function isSuccess(result: IResult): boolean { @@ -26,17 +26,21 @@ export namespace Result { throw new ErrContract(`${result.getReturnCode()}: ${result.getReturnMessage()}`); } - export function outputTyped(endpointDefinition: EndpointDefinition, result: IResult) { - result.assertSuccess(); - - let buffers = result.outputUntyped(); - let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition.output); + export function unpackQueryOutput(endpoint: EndpointDefinition, queryResponse: QueryResponse) { + queryResponse.assertSuccess(); + let buffers = queryResponse.getReturnDataParts(); + let typedValues = new ArgSerializer().buffersToValues(buffers, endpoint.output); + let values = typedValues.map((value) => value?.valueOf()); + if (values.length <= 1) { + return values[0]; + } return values; } - - export function unpackOutput(result: IResult) { - let values = result.outputTyped().map((value) => value?.valueOf()); + export function unpackExecutionOutput(endpoint: EndpointDefinition, result: TypedResult) { + let buffers = result.outputUntyped(); + let typedValues = new ArgSerializer().buffersToValues(buffers, endpoint.output); + let values = typedValues.map((value) => value?.valueOf()); if (values.length <= 1) { return values[0]; } diff --git a/src/testutils/mockProvider.ts b/src/testutils/mockProvider.ts index a9a12664..b46b230c 100644 --- a/src/testutils/mockProvider.ts +++ b/src/testutils/mockProvider.ts @@ -88,16 +88,18 @@ export class MockProvider implements IProvider { this.queryContractResponders.push(new QueryContractResponder(predicate, response)); } - mockGetTransactionWithAnyHashAsNotarizedWithContractResults(contractResults: SmartContractResultItem[]) { + mockGetTransactionWithAnyHashAsNotarizedWithOneResult(returnCodeAndData: string) { + let contractResult = new SmartContractResultItem({ nonce: new Nonce(1), data: returnCodeAndData }); + let predicate = (_hash: TransactionHash) => true; let response = new TransactionOnNetwork({ status: new TransactionStatus("executed"), hyperblockNonce: DummyHyperblockNonce, hyperblockHash: DummyHyperblockHash, - results: new SmartContractResults(contractResults) + results: new SmartContractResults([contractResult]) }); - this.getTransactionResponders.push(new GetTransactionResponder(predicate, response)); + this.getTransactionResponders.unshift(new GetTransactionResponder(predicate, response)); } async mockTransactionTimeline(transaction: Transaction, timelinePoints: any[]): Promise { From a6e7b3e72aa11a8497f669bbb9fa06a96ff0adcb Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 15:29:01 +0200 Subject: [PATCH 10/37] For tests, use new lottery-esdt contract. --- src/smartcontracts/codec/binary.spec.ts | 15 ++-- .../interaction.local.net.spec.ts | 12 +-- src/smartcontracts/interaction.spec.ts | 40 +++++----- src/smartcontracts/interactionChecker.spec.ts | 16 ++-- .../smartContract.local.net.spec.ts | 4 +- .../typesystem/abiRegistry.spec.ts | 38 +++++---- .../wrapper/contractWrapper.local.net.spec.ts | 6 +- .../wrapper/contractWrapper.spec.ts | 23 +++--- .../wrapper/deprecatedContractResults.ts | 5 +- ...ry_egld.abi.json => lottery-esdt.abi.json} | 74 +++++++++++++++--- src/testdata/lottery-esdt.wasm | Bin 0 -> 12532 bytes src/testdata/lottery_egld.wasm | Bin 20616 -> 0 bytes 12 files changed, 149 insertions(+), 84 deletions(-) rename src/testdata/{lottery_egld.abi.json => lottery-esdt.abi.json} (70%) create mode 100755 src/testdata/lottery-esdt.wasm delete mode 100755 src/testdata/lottery_egld.wasm diff --git a/src/smartcontracts/codec/binary.spec.ts b/src/smartcontracts/codec/binary.spec.ts index b7dbf9b1..34401061 100644 --- a/src/smartcontracts/codec/binary.spec.ts +++ b/src/smartcontracts/codec/binary.spec.ts @@ -189,29 +189,27 @@ describe("test binary codec (advanced)", () => { let fooType = new StructType( "Foo", [ + new FieldDefinition("token_identifier", "", new TokenIdentifierType()), new FieldDefinition("ticket_price", "", new BigUIntType()), new FieldDefinition("tickets_left", "", new U32Type()), new FieldDefinition("deadline", "", new U64Type()), new FieldDefinition("max_entries_per_user", "", new U32Type()), new FieldDefinition("prize_distribution", "", new BytesType()), - new FieldDefinition("whitelist", "", new ListType(new AddressType())), - new FieldDefinition("current_ticket_number", "", new U32Type()), new FieldDefinition("prize_pool", "", new BigUIntType()) ] ); let fooStruct = new Struct(fooType, [ - new Field(new BigUIntValue(Balance.egld(10).valueOf()), "ticket_price"), + new Field(new TokenIdentifierValue(Buffer.from("lucky-token")), "token_identifier"), + new Field(new BigUIntValue(1), "ticket_price"), new Field(new U32Value(0), "tickets_left"), new Field(new U64Value(new BigNumber("0x000000005fc2b9db")), "deadline"), new Field(new U32Value(0xffffffff), "max_entries_per_user"), new Field(new BytesValue(Buffer.from([0x64])), "prize_distribution"), - new Field(new List(new ListType(new AddressType()), []), "whitelist"), - new Field(new U32Value(9472), "current_ticket_number"), new Field(new BigUIntValue(new BigNumber("94720000000000000000000")), "prize_pool") ]); - let encodedExpected = serialized("[00000008|8ac7230489e80000] [00000000] [000000005fc2b9db] [ffffffff] [00000001|64] [00000000] [00002500] [0000000a|140ec80fa7ee88000000]"); + let encodedExpected = serialized("[0000000b|6c75636b792d746f6b656e] [00000001|01] [00000000] [000000005fc2b9db] [ffffffff] [00000001|64] [0000000a|140ec80fa7ee88000000]"); let encoded = codec.encodeNested(fooStruct); assert.deepEqual(encoded, encodedExpected); @@ -221,13 +219,12 @@ describe("test binary codec (advanced)", () => { let plainFoo = decoded.valueOf(); assert.deepEqual(plainFoo, { - ticket_price: new BigNumber("10000000000000000000"), + token_identifier: Buffer.from("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]), - whitelist: [], - current_ticket_number: new BigNumber(9472), prize_pool: new BigNumber("94720000000000000000000") }); }); diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 497516c8..3c26323d 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -96,10 +96,10 @@ describe("test smart contract interactor", function () { assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(0)); }); - it("should interact with 'lottery_egld' (local testnet)", async function () { + it("should interact with 'lottery-esdt' (local testnet)", async function () { this.timeout(120000); - let abiRegistry = await loadAbiRegistry(["src/testdata/lottery_egld.abi.json"]); + let abiRegistry = await loadAbiRegistry(["src/testdata/lottery-esdt.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ abi: abi }); let controller = new DefaultInteractionController(abi, provider); @@ -108,7 +108,7 @@ describe("test smart contract interactor", function () { // because the Transaction objects created under the hood point to the "default" NetworkConfig. await NetworkConfig.getDefault().sync(provider); await alice.sync(provider); - await deploy(contract, "src/testdata/lottery_egld.wasm", new GasLimit(100000000), []); + await deploy(contract, "src/testdata/lottery-esdt.wasm", new GasLimit(100000000), []); let startInteraction = contract.methods.start([ BytesValue.fromUTF8("lucky"), @@ -118,15 +118,15 @@ describe("test smart contract interactor", function () { OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), - ]).withGasLimit(new GasLimit(15000000)); + ]).withGasLimit(new GasLimit(30000000)); let lotteryStatusInteraction = contract.methods.status([ BytesValue.fromUTF8("lucky") - ]).withGasLimit(new GasLimit(15000000)); + ]).withGasLimit(new GasLimit(30000000)); let getLotteryInfoInteraction = contract.methods.lotteryInfo([ BytesValue.fromUTF8("lucky") - ]).withGasLimit(new GasLimit(15000000)); + ]).withGasLimit(new GasLimit(30000000)); // start() let startTransaction = startInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 9e520436..d1f3e764 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -1,6 +1,6 @@ import { DefaultInteractionController } from "./interactionController"; import { SmartContract } from "./smartContract"; -import { BigUIntValue, OptionValue, U32Value } from "./typesystem"; +import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadTestWallets, @@ -22,7 +22,6 @@ import BigNumber from "bignumber.js"; import { BytesValue } from "./typesystem/bytes"; import { Token, TokenType } from "../token"; import { createBalanceBuilder } from "../balanceBuilder"; -import { SmartContractResultItem } from "./smartContractResults"; describe("test smart contract interactor", function() { let dummyAddress = new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); @@ -215,10 +214,10 @@ describe("test smart contract interactor", function() { assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(5)); }); - it("should interact with 'lottery_egld'", async function() { + it("should interact with 'lottery-esdt'", async function() { setupUnitTestWatcherTimeouts(); - let abiRegistry = await loadAbiRegistry(["src/testdata/lottery_egld.abi.json"]); + let abiRegistry = await loadAbiRegistry(["src/testdata/lottery-esdt.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); let controller = new DefaultInteractionController(abi, provider); @@ -227,12 +226,14 @@ describe("test smart contract interactor", function() { contract.methods .start([ BytesValue.fromUTF8("lucky"), - new BigUIntValue(Balance.egld(1).valueOf()), + new TokenIdentifierValue(Buffer.from("lucky-token")), + new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newMissing(), OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), + new OptionalValue(new OptionalType(new BigUIntType())) ]) .withGasLimit(new GasLimit(5000000)) ); @@ -241,8 +242,8 @@ describe("test smart contract interactor", function() { contract.methods.status([BytesValue.fromUTF8("lucky")]).withGasLimit(new GasLimit(5000000)) ); - let lotteryInfoInteraction = ( - contract.methods.lotteryInfo([BytesValue.fromUTF8("lucky")]).withGasLimit(new GasLimit(5000000)) + let getLotteryInfoInteraction = ( + contract.methods.getLotteryInfo([BytesValue.fromUTF8("lucky")]).withGasLimit(new GasLimit(5000000)) ); // start() @@ -251,7 +252,7 @@ describe("test smart contract interactor", function() { provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b"); let { bundle: { returnCode: startReturnCode, values: startReturnValues } } = await controller.execute(startInteraction, startTransaction); - assert.equal(startTransaction.getData().toString(), "start@6c75636b79@0de0b6b3a7640000@@@0100000001@@"); + assert.equal(startTransaction.getData().toString(), "start@6c75636b79@6c75636b792d746f6b656e@01@@@0100000001@@"); assert.isTrue(startReturnCode.equals(ReturnCode.Ok)); assert.lengthOf(startReturnValues, 0); @@ -267,24 +268,23 @@ describe("test smart contract interactor", function() { assert.deepEqual(statusFirstValue!.valueOf(), { name: "Running", fields: [] }); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let lotteryInfoTransaction = lotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); - await alice.signer.sign(lotteryInfoTransaction); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000"); - let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue} } = await controller.execute(lotteryInfoInteraction, lotteryInfoTransaction); + let getLotteryInfoTransaction = getLotteryInfoInteraction.withNonce(new Nonce(15)).buildTransaction(); + await alice.signer.sign(getLotteryInfoTransaction); + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000"); + let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue} } = await controller.execute(getLotteryInfoInteraction, getLotteryInfoTransaction); - assert.equal(lotteryInfoTransaction.getData().toString(), "lotteryInfo@6c75636b79"); + assert.equal(getLotteryInfoTransaction.getData().toString(), "getLotteryInfo@6c75636b79"); assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); assert.lengthOf(infoReturnValues, 1); assert.deepEqual(infoFirstValue!.valueOf(), { - ticket_price: new BigNumber("1000000000000000000"), - tickets_left: new BigNumber(800), - deadline: new BigNumber("1611835398"), - max_entries_per_user: new BigNumber(1), + token_identifier: Buffer.from("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]), - whitelist: [], - current_ticket_number: new BigNumber(0), - prize_pool: new BigNumber("0"), + prize_pool: new BigNumber("94720000000000000000000") }); }); }); diff --git a/src/smartcontracts/interactionChecker.spec.ts b/src/smartcontracts/interactionChecker.spec.ts index f964b56a..6c7cb98c 100644 --- a/src/smartcontracts/interactionChecker.spec.ts +++ b/src/smartcontracts/interactionChecker.spec.ts @@ -1,7 +1,7 @@ import * as errors from "../errors"; import { InteractionChecker } from "./interactionChecker"; import { SmartContract } from "./smartContract"; -import { BigUIntValue, OptionValue, U64Value } from "./typesystem"; +import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value, U64Value } from "./typesystem"; import { loadAbiRegistry } from "../testutils"; import { SmartContractAbi } from "./abi"; import { Address } from "../address"; @@ -35,7 +35,7 @@ describe("integration tests: test checker within interactor", function () { }); it("should detect errors for 'lottery'", async function () { - let abiRegistry = await loadAbiRegistry(["src/testdata/lottery_egld.abi.json"]); + let abiRegistry = await loadAbiRegistry(["src/testdata/lottery-esdt.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); let endpoint = abi.getEndpoint("start"); @@ -48,20 +48,22 @@ describe("integration tests: test checker within interactor", function () { OptionValue.newMissing() ]); checker.checkInteraction(interaction, endpoint); - }, errors.ErrContractInteraction, "bad arguments, expected: 7, got: 3"); + }, errors.ErrContractInteraction, "bad arguments, expected: 9, got: 3"); // Bad types (U64 instead of U32) assert.throw(() => { let interaction = contract.methods.start([ BytesValue.fromUTF8("lucky"), - new BigUIntValue(Balance.egld(1).valueOf()), - OptionValue.newMissing(), + new TokenIdentifierValue(Buffer.from("lucky-token")), + new BigUIntValue(1), OptionValue.newMissing(), - OptionValue.newProvided(new U64Value(new BigNumber(1))), + OptionValue.newProvided(new U32Value(1)), + OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), + new OptionalValue(new OptionalType(new BigUIntType())) ]); checker.checkInteraction(interaction, endpoint); - }, errors.ErrContractInteraction, "type mismatch at index 4, expected: Option, got: Option"); + }, errors.ErrContractInteraction, "type mismatch at index 4, expected: Option, got: Option"); }); }); diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index b57da311..677b6c03 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -229,7 +229,7 @@ describe("test on local testnet", function () { // Deploy let contract = new SmartContract({}); let transactionDeploy = contract.deploy({ - code: await loadContractCode("src/testdata/lottery_egld.wasm"), + code: await loadContractCode("src/testdata/lottery-esdt.wasm"), gasLimit: new GasLimit(100000000), initArguments: [] }); @@ -243,7 +243,7 @@ describe("test on local testnet", function () { // Start let transactionStart = contract.call({ func: new ContractFunction("start"), - gasLimit: new GasLimit(15000000), + gasLimit: new GasLimit(50000000), args: [ BytesValue.fromUTF8("foobar"), new BigUIntValue(Balance.egld(1).valueOf()), diff --git a/src/smartcontracts/typesystem/abiRegistry.spec.ts b/src/smartcontracts/typesystem/abiRegistry.spec.ts index e798814d..a45a5f31 100644 --- a/src/smartcontracts/typesystem/abiRegistry.spec.ts +++ b/src/smartcontracts/typesystem/abiRegistry.spec.ts @@ -3,11 +3,13 @@ import { extendAbiRegistry, loadAbiRegistry } from "../../testutils"; import { BinaryCodec } from "../codec"; import { AbiRegistry } from "./abiRegistry"; import { AddressType } from "./address"; +import { OptionalType } from "./algebraic"; import { BytesType } from "./bytes"; import { EnumType } from "./enum"; import { ListType, OptionType } from "./generic"; -import { BigUIntType, I64Type, U32Type, U8Type } from "./numerical"; +import { BigUIntType, I64Type, U32Type, U64Type, U8Type } from "./numerical"; import { StructType } from "./struct"; +import { TokenIdentifierType } from "./tokenIdentifier"; describe("test abi registry", () => { it("should extend", async () => { @@ -23,12 +25,12 @@ describe("test abi registry", () => { assert.lengthOf(registry.customTypes, 0); assert.lengthOf(registry.getInterface("counter").endpoints, 3); - await extendAbiRegistry(registry, "src/testdata/lottery_egld.abi.json"); + await extendAbiRegistry(registry, "src/testdata/lottery-esdt.abi.json"); assert.lengthOf(registry.interfaces, 3); assert.lengthOf(registry.customTypes, 2); - assert.lengthOf(registry.getInterface("Lottery").endpoints, 6); - assert.lengthOf(registry.getStruct("LotteryInfo").getFieldsDefinitions(), 8); + assert.lengthOf(registry.getInterface("Lottery").endpoints, 7); + assert.lengthOf(registry.getStruct("LotteryInfo").getFieldsDefinitions(), 7); assert.lengthOf(registry.getEnum("Status").variants, 3); }); @@ -36,7 +38,7 @@ describe("test abi registry", () => { let registry = await loadAbiRegistry([ "src/testdata/answer.abi.json", "src/testdata/counter.abi.json", - "src/testdata/lottery_egld.abi.json", + "src/testdata/lottery-esdt.abi.json", ]); // Ultimate answer @@ -53,13 +55,22 @@ describe("test abi registry", () => { let lottery = registry.getInterface("Lottery"); let start = lottery.getEndpoint("start"); let getStatus = lottery.getEndpoint("status"); - let getLotteryInfo = lottery.getEndpoint("lotteryInfo"); + let getLotteryInfo = lottery.getEndpoint("getLotteryInfo"); assert.instanceOf(start.input[0].type, BytesType); - assert.instanceOf(start.input[1].type, BigUIntType); - assert.instanceOf(start.input[2].type, OptionType); - assert.instanceOf(start.input[2].type.getFirstTypeParameter(), U32Type); - assert.instanceOf(start.input[6].type.getFirstTypeParameter(), ListType); - assert.instanceOf(start.input[6].type.getFirstTypeParameter().getFirstTypeParameter(), AddressType); + assert.instanceOf(start.input[1].type, TokenIdentifierType); + assert.instanceOf(start.input[2].type, BigUIntType); + assert.instanceOf(start.input[3].type, OptionType); + assert.instanceOf(start.input[3].type.getFirstTypeParameter(), U32Type); + assert.instanceOf(start.input[4].type, OptionType); + assert.instanceOf(start.input[4].type.getFirstTypeParameter(), U64Type); + assert.instanceOf(start.input[5].type, OptionType); + assert.instanceOf(start.input[5].type.getFirstTypeParameter(), U32Type); + assert.instanceOf(start.input[6].type, OptionType); + assert.instanceOf(start.input[6].type.getFirstTypeParameter(), BytesType); + assert.instanceOf(start.input[7].type.getFirstTypeParameter(), ListType); + assert.instanceOf(start.input[7].type.getFirstTypeParameter().getFirstTypeParameter(), AddressType); + assert.instanceOf(start.input[8].type, OptionalType); + assert.instanceOf(start.input[8].type.getFirstTypeParameter(), BigUIntType); assert.instanceOf(getStatus.input[0].type, BytesType); assert.instanceOf(getStatus.output[0].type, EnumType); assert.equal(getStatus.output[0].type.getName(), "Status"); @@ -68,9 +79,8 @@ describe("test abi registry", () => { assert.equal(getLotteryInfo.output[0].type.getName(), "LotteryInfo"); let fieldDefinitions = (getLotteryInfo.output[0].type).getFieldsDefinitions(); - assert.instanceOf(fieldDefinitions[0].type, BigUIntType); - assert.instanceOf(fieldDefinitions[5].type, ListType); - assert.instanceOf(fieldDefinitions[5].type.getFirstTypeParameter(), AddressType); + assert.instanceOf(fieldDefinitions[0].type, TokenIdentifierType); + assert.instanceOf(fieldDefinitions[5].type, BytesType); }); it("binary codec correctly decodes perform action result", async () => { diff --git a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts index d9b8aae7..3eb55ce7 100644 --- a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts @@ -44,14 +44,14 @@ describe("test smart contract interactor", function () { assert.deepEqual(await counter.call.decrement(), new BigNumber(0)); }); - it("should interact with 'lottery_egld' (local testnet)", async function () { + it("should interact with 'lottery-esdt' (local testnet)", async function () { this.timeout(120000); - let lottery = await erdSys.loadWrapper("src/testdata", "lottery_egld"); + let lottery = await erdSys.loadWrapper("src/testdata", "lottery-esdt"); await lottery.sender(alice).gas(100_000_000).call.deploy(); - lottery.gas(15_000_000); + lottery.gas(50_000_000); await lottery.call.start("lucky", Balance.egld(1), null, null, 1, null, null); let status = await lottery.query.status("lucky"); diff --git a/src/smartcontracts/wrapper/contractWrapper.spec.ts b/src/smartcontracts/wrapper/contractWrapper.spec.ts index e3f5ea49..98c664ec 100644 --- a/src/smartcontracts/wrapper/contractWrapper.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.spec.ts @@ -66,35 +66,34 @@ describe("test smart contract wrapper", async function() { assert.deepEqual(decrementResult, new BigNumber(7)); }); - it("should interact with 'lottery_egld'", async function() { + it("should interact with 'lottery-esdt'", async function() { setupUnitTestWatcherTimeouts(); - let lottery = await erdSys.loadWrapper("src/testdata", "lottery_egld"); + let lottery = await erdSys.loadWrapper("src/testdata", "lottery-esdt"); lottery .address(dummyAddress) .sender(alice) .gas(5_000_000); - await mockCall(provider, "@6f6b", lottery.call.start("lucky", Egld(1), null, null, 1, null, null)); + await mockCall(provider, "@6f6b", lottery.call.start("lucky", "lucky-token", 1, null, null, 1, null, null)); let status = await mockCall(provider, "@6f6b@01", lottery.call.status("lucky")); assert.deepEqual(status, { name: "Running", fields: [] }); let info = await mockCall( provider, - "@6f6b@000000080de0b6b3a764000000000320000000006012a806000000010000000164000000000000000000000000", - lottery.call.lotteryInfo("lucky") + "@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000", + lottery.call.getLotteryInfo("lucky") ); assert.deepEqual(info, { - ticket_price: new BigNumber("1000000000000000000"), - tickets_left: new BigNumber(800), - deadline: new BigNumber("1611835398"), - max_entries_per_user: new BigNumber(1), + token_identifier: Buffer.from("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]), - whitelist: [], - current_ticket_number: new BigNumber(0), - prize_pool: new BigNumber("0"), + prize_pool: new BigNumber("94720000000000000000000") }); }); }); diff --git a/src/smartcontracts/wrapper/deprecatedContractResults.ts b/src/smartcontracts/wrapper/deprecatedContractResults.ts index f91c1b11..573e62c5 100644 --- a/src/smartcontracts/wrapper/deprecatedContractResults.ts +++ b/src/smartcontracts/wrapper/deprecatedContractResults.ts @@ -120,7 +120,10 @@ export class TypedResult extends SmartContractResultItem implements Result.IResu return this.getDataParts().slice(2); } + /** + * @deprecated The return message isn't available on SmartContractResultItem (not provided by the API). + */ getReturnMessage(): string { - return "TODO: the return message isn't available on SmartContractResultItem (not provided by the API)"; + return this.getReturnCode().toString(); } } diff --git a/src/testdata/lottery_egld.abi.json b/src/testdata/lottery-esdt.abi.json similarity index 70% rename from src/testdata/lottery_egld.abi.json rename to src/testdata/lottery-esdt.abi.json index e4798451..a9fa48c5 100644 --- a/src/testdata/lottery_egld.abi.json +++ b/src/testdata/lottery-esdt.abi.json @@ -1,4 +1,21 @@ { + "buildInfo": { + "rustc": { + "version": "1.60.0-nightly", + "commitHash": "c5c610aad0a012a9228ecb83cc19e77111a52140", + "commitDate": "2022-02-14", + "channel": "Nightly", + "short": "rustc 1.60.0-nightly (c5c610aad 2022-02-14)" + }, + "contractCrate": { + "name": "lottery-esdt", + "version": "0.0.0" + }, + "framework": { + "name": "elrond-wasm", + "version": "0.30.0" + } + }, "name": "Lottery", "constructor": { "inputs": [], @@ -7,11 +24,16 @@ "endpoints": [ { "name": "start", + "mutability": "mutable", "inputs": [ { "name": "lottery_name", "type": "bytes" }, + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, { "name": "ticket_price", "type": "BigUint" @@ -35,17 +57,27 @@ { "name": "opt_whitelist", "type": "Option>" + }, + { + "name": "opt_burn_percentage", + "type": "optional", + "multi_arg": true } ], "outputs": [] }, { "name": "createLotteryPool", + "mutability": "mutable", "inputs": [ { "name": "lottery_name", "type": "bytes" }, + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, { "name": "ticket_price", "type": "BigUint" @@ -69,14 +101,20 @@ { "name": "opt_whitelist", "type": "Option>" + }, + { + "name": "opt_burn_percentage", + "type": "optional", + "multi_arg": true } ], "outputs": [] }, { "name": "buy_ticket", + "mutability": "mutable", "payableInTokens": [ - "EGLD" + "*" ], "inputs": [ { @@ -88,6 +126,7 @@ }, { "name": "determine_winner", + "mutability": "mutable", "inputs": [ { "name": "lottery_name", @@ -98,6 +137,7 @@ }, { "name": "status", + "mutability": "readonly", "inputs": [ { "name": "lottery_name", @@ -111,7 +151,8 @@ ] }, { - "name": "lotteryInfo", + "name": "getLotteryInfo", + "mutability": "readonly", "inputs": [ { "name": "lottery_name", @@ -123,12 +164,33 @@ "type": "LotteryInfo" } ] + }, + { + "name": "getLotteryWhitelist", + "mutability": "readonly", + "inputs": [ + { + "name": "lottery_name", + "type": "bytes" + } + ], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] } ], + "hasCallback": false, "types": { "LotteryInfo": { "type": "struct", "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, { "name": "ticket_price", "type": "BigUint" @@ -149,14 +211,6 @@ "name": "prize_distribution", "type": "bytes" }, - { - "name": "whitelist", - "type": "List
" - }, - { - "name": "current_ticket_number", - "type": "u32" - }, { "name": "prize_pool", "type": "BigUint" diff --git a/src/testdata/lottery-esdt.wasm b/src/testdata/lottery-esdt.wasm new file mode 100755 index 0000000000000000000000000000000000000000..1d031ace204b2b0349bc1a20abfe6e47649f24d0 GIT binary patch literal 12532 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 m(mB}d)pWVuYRo>8XnV literal 0 HcmV?d00001 diff --git a/src/testdata/lottery_egld.wasm b/src/testdata/lottery_egld.wasm deleted file mode 100755 index 1fd83f0fd62e39d115e90d8d58ae80edcfee2afb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20616 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 Date: Sun, 20 Mar 2022 19:02:14 +0200 Subject: [PATCH 11/37] Fix integration tests. --- src/proxyProvider.ts | 2 +- .../interaction.local.net.spec.ts | 14 +++++------ src/smartcontracts/interactionController.ts | 6 +++++ src/smartcontracts/query.main.net.spec.ts | 8 +++---- src/smartcontracts/resultsParser.ts | 23 +++++++++++++++++-- .../smartContract.local.net.spec.ts | 10 ++++---- .../wrapper/contractWrapper.local.net.spec.ts | 12 ++++------ src/transaction.dev.net.spec.ts | 9 +++----- 8 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/proxyProvider.ts b/src/proxyProvider.ts index 65f9b7b9..2650e25e 100644 --- a/src/proxyProvider.ts +++ b/src/proxyProvider.ts @@ -70,7 +70,7 @@ export class ProxyProvider implements IProvider { return this.doPostGeneric("vm-values/query", data, (response) => QueryResponse.fromHttpResponse(response.data || response.vmOutput) ); - } catch (err) { + } catch (err: any) { throw errors.ErrContractQuery.increaseSpecificity(err); } } diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 3c26323d..b3e4de9d 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -1,13 +1,12 @@ import { DefaultInteractionController } from "./interactionController"; import { SmartContract } from "./smartContract"; -import { BigUIntValue, OptionValue, TypedValue, U32Value } from "./typesystem"; +import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, TypedValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadContractCode, loadTestWallets, TestWallet } from "../testutils"; import { SmartContractAbi } from "./abi"; import { assert } from "chai"; import { Interaction } from "./interaction"; import { GasLimit } from "../networkParams"; import { ReturnCode } from "./returnCode"; -import { Balance } from "../balance"; import BigNumber from "bignumber.js"; import { NetworkConfig } from "../networkConfig"; import { BytesValue } from "./typesystem/bytes"; @@ -112,19 +111,21 @@ describe("test smart contract interactor", function () { let startInteraction = contract.methods.start([ BytesValue.fromUTF8("lucky"), - new BigUIntValue(Balance.egld(1).valueOf()), + new TokenIdentifierValue(Buffer.from("EGLD")), + new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newMissing(), OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), + new OptionalValue(new OptionalType(new BigUIntType())) ]).withGasLimit(new GasLimit(30000000)); let lotteryStatusInteraction = contract.methods.status([ BytesValue.fromUTF8("lucky") ]).withGasLimit(new GasLimit(30000000)); - let getLotteryInfoInteraction = contract.methods.lotteryInfo([ + let getLotteryInfoInteraction = contract.methods.getLotteryInfo([ BytesValue.fromUTF8("lucky") ]).withGasLimit(new GasLimit(30000000)); @@ -155,12 +156,11 @@ describe("test smart contract interactor", function () { delete info.deadline; assert.deepEqual(info, { - ticket_price: new BigNumber("1000000000000000000"), + token_identifier: Buffer.from("EGLD"), + ticket_price: new BigNumber("1"), tickets_left: new BigNumber(800), max_entries_per_user: new BigNumber(1), prize_distribution: Buffer.from([0x64]), - whitelist: [], - current_ticket_number: new BigNumber(0), prize_pool: new BigNumber("0") }); }); diff --git a/src/smartcontracts/interactionController.ts b/src/smartcontracts/interactionController.ts index fe5e8b37..f4893bbf 100644 --- a/src/smartcontracts/interactionController.ts +++ b/src/smartcontracts/interactionController.ts @@ -7,6 +7,7 @@ import { ContractFunction } from "./function"; import { ResultsParser } from "./resultsParser"; import { InteractionChecker, NullInteractionChecker } from "./interactionChecker"; import { EndpointDefinition } from "./typesystem"; +import { Logger } from "../logger"; /** * Internal interface: the smart contract ABI, as seen from the perspective of an {@link InteractionController}. @@ -44,6 +45,8 @@ export class InteractionController { * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand */ async execute(interaction: Interaction, signedInteractionTransaction: Transaction): Promise<{ transaction: TransactionOnNetwork, bundle: ContractOutcomeBundle }> { + Logger.info(`InteractionController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${signedInteractionTransaction.getHash()}`) + let endpoint = this.getEndpoint(interaction); this.checker.checkInteraction(interaction, endpoint); @@ -52,6 +55,9 @@ export class InteractionController { await signedInteractionTransaction.awaitExecuted(this.provider); let transactionOnNetwork = await signedInteractionTransaction.getAsOnNetwork(this.provider); let outcomeBundle = this.parser.parseOutcome(transactionOnNetwork, endpoint); + + Logger.info(`InteractionController.execute [end]: function = ${interaction.getFunction()}, transaction = ${signedInteractionTransaction.getHash()}, return code = ${outcomeBundle.returnCode}`) + return { transaction: transactionOnNetwork, bundle: outcomeBundle }; } diff --git a/src/smartcontracts/query.main.net.spec.ts b/src/smartcontracts/query.main.net.spec.ts index b5d92e55..251dfe94 100644 --- a/src/smartcontracts/query.main.net.spec.ts +++ b/src/smartcontracts/query.main.net.spec.ts @@ -26,7 +26,7 @@ describe("test queries on mainnet", function () { assert.isTrue(response.isSuccess()); assert.lengthOf(response.returnData, 1); - assert.isAtLeast(response.gasUsed.valueOf(), 20000000); + assert.isAtLeast(response.gasUsed.valueOf(), 1000000); assert.isAtMost(response.gasUsed.valueOf(), 50000000); }); @@ -38,7 +38,7 @@ describe("test queries on mainnet", function () { }); assert.isTrue(response.isSuccess()); - assert.isAtLeast(response.returnData.length, 20000); + assert.isAtLeast(response.returnData.length, 42); }); it("delegation: should getClaimableRewards", async function () { @@ -46,12 +46,12 @@ describe("test queries on mainnet", function () { // First, expect an error (bad arguments): try { - await delegationContract.runQuery(provider, { + let kk = await delegationContract.runQuery(provider, { func: new ContractFunction("getClaimableRewards") }); throw new errors.ErrTest("unexpected"); - } catch (err) { + } catch (err: any) { assert.instanceOf(err, errors.ErrContractQuery); assert.include(err.toString(), "wrong number of arguments"); } diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index 2ac7eb92..2ae746e1 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -21,12 +21,31 @@ export class ResultsParser implements IResultsParser { }; } + /** + * TODO: Improve this function. Currently, the implementation makes some (possibly incorrect) assumptions on the SCR & logs construction logic. + * + * @param transaction The transaction holding the contract execution outcome (results and logs). + * @param endpoint The endpoint definition (ABI) + */ parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): ContractOutcomeBundle { let resultItems = transaction.results.getAll(); - // TODO: Fix! The condition below IS NOT CORRECT (not sufficient). + // TODO: Fix! The filtering condition below IS NOT necessarily CORRECT (not sufficient). let resultItemWithReturnData = resultItems.find(item => item.nonce.valueOf() != 0); + if (!resultItemWithReturnData) { - throw new ErrInvariantFailed("SCR with return data not found"); + let returnCode = ReturnCode.Unknown; + + if (transaction.logs.findEventByIdentifier("completedTxEvent")) { + returnCode = ReturnCode.Ok; + } else if (transaction.logs.findEventByIdentifier("signalError")) { + returnCode = ReturnCode.UserError; + } + + return { + returnCode: returnCode, + returnMessage: returnCode.toString(), + values: [] + }; } let parts = resultItemWithReturnData.getDataParts(); diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 677b6c03..344ec485 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -8,7 +8,7 @@ import { loadContractCode } from "../testutils"; import { Logger } from "../logger"; import { assert } from "chai"; import { Balance } from "../balance"; -import { AddressValue, BigUIntValue, OptionValue, U32Value } from "./typesystem"; +import { AddressValue, BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { decodeUnsignedNumber } from "./codec"; import { BytesValue } from "./typesystem/bytes"; import { chooseProxyProvider } from "../interactive"; @@ -245,13 +245,15 @@ describe("test on local testnet", function () { func: new ContractFunction("start"), gasLimit: new GasLimit(50000000), args: [ - BytesValue.fromUTF8("foobar"), - new BigUIntValue(Balance.egld(1).valueOf()), + BytesValue.fromUTF8("lucky"), + new TokenIdentifierValue(Buffer.from("EGLD")), + new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newMissing(), OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), + new OptionalValue(new OptionalType(new BigUIntType())) ] }); @@ -278,7 +280,7 @@ describe("test on local testnet", function () { let queryResponse = await contract.runQuery(provider, { func: new ContractFunction("status"), args: [ - BytesValue.fromUTF8("foobar") + BytesValue.fromUTF8("lucky") ] }); assert.equal(decodeUnsignedNumber(queryResponse.getReturnDataParts()[0]), 1); diff --git a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts index 3eb55ce7..a4b8a3cc 100644 --- a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts @@ -3,10 +3,9 @@ import { BigNumber } from "bignumber.js"; import { TestWallet } from "../../testutils"; import { SystemWrapper } from "./systemWrapper"; import { setupInteractive } from "../../interactive"; -import { Balance } from "../../balance"; -describe("test smart contract interactor", function () { +describe("test smart contract wrapper", function () { let erdSys: SystemWrapper; let alice: TestWallet; @@ -52,22 +51,21 @@ describe("test smart contract interactor", function () { await lottery.sender(alice).gas(100_000_000).call.deploy(); lottery.gas(50_000_000); - await lottery.call.start("lucky", Balance.egld(1), null, null, 1, null, null); + await lottery.call.start("lucky", "EGLD", 1, null, null, 1, null, null); let status = await lottery.query.status("lucky"); assert.equal(status.valueOf().name, "Running"); - let info = await lottery.query.lotteryInfo("lucky"); + let info = await lottery.query.getLotteryInfo("lucky"); // Ignore "deadline" field in our test delete info.deadline; assert.deepEqual(info, { - ticket_price: new BigNumber("1000000000000000000"), + token_identifier: Buffer.from("EGLD"), + ticket_price: new BigNumber("1"), tickets_left: new BigNumber(800), max_entries_per_user: new BigNumber(1), prize_distribution: Buffer.from([0x64]), - whitelist: [], - current_ticket_number: new BigNumber(0), prize_pool: new BigNumber("0") }); }); diff --git a/src/transaction.dev.net.spec.ts b/src/transaction.dev.net.spec.ts index 9ff8183f..fe34c2b6 100644 --- a/src/transaction.dev.net.spec.ts +++ b/src/transaction.dev.net.spec.ts @@ -48,19 +48,16 @@ describe("test transactions on devnet", function () { transactionOnProxy.hyperblockNonce = new Nonce(0); transactionOnProxy.hyperblockHash = new Hash(""); - let immediateContractResultOnAPI: SmartContractResultItem = (transactionOnAPI).results.immediate; - let contractResultsOnAPI: SmartContractResultItem[] = (transactionOnAPI).results.items; - let resultingCallsOnAPI: SmartContractResultItem[] = (transactionOnAPI).results.resultingCalls; - let allContractResults = [immediateContractResultOnAPI].concat(resultingCallsOnAPI).concat(contractResultsOnAPI); + let contractResultsOnAPI: SmartContractResultItem[] = transactionOnAPI.results.getAll(); // Important issue (existing bug)! When working with TransactionOnNetwork objects, SCRs cannot be parsed correctly from API, only from Proxy. // On API response, base64 decode "data" from smart contract results: - for (const item of allContractResults) { + for (const item of contractResultsOnAPI) { item.data = Buffer.from(item.data, "base64").toString(); } // On API response, convert "callType" of smart contract results to a number: - for (const item of allContractResults) { + for (const item of contractResultsOnAPI) { item.callType = Number(item.callType); } From 3d2d13334fe05a380814a7da68380df7425aaf0c Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 20:03:22 +0200 Subject: [PATCH 12/37] Typed vs. untyped parsing. Fix tests. --- .../interaction.local.net.spec.ts | 4 +- src/smartcontracts/interactionController.ts | 6 +-- src/smartcontracts/interface.ts | 15 ++++++-- src/smartcontracts/query.main.net.spec.ts | 16 +++----- src/smartcontracts/resultsParser.ts | 37 +++++++++++++++---- .../smartContract.local.net.spec.ts | 25 +++++++++---- .../wrapper/contractWrapper.local.net.spec.ts | 4 +- src/transactionLogs.ts | 9 ++++- 8 files changed, 79 insertions(+), 37 deletions(-) diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index b3e4de9d..d426ab2d 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -123,11 +123,11 @@ describe("test smart contract interactor", function () { let lotteryStatusInteraction = contract.methods.status([ BytesValue.fromUTF8("lucky") - ]).withGasLimit(new GasLimit(30000000)); + ]).withGasLimit(new GasLimit(5000000)); let getLotteryInfoInteraction = contract.methods.getLotteryInfo([ BytesValue.fromUTF8("lucky") - ]).withGasLimit(new GasLimit(30000000)); + ]).withGasLimit(new GasLimit(5000000)); // start() let startTransaction = startInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); diff --git a/src/smartcontracts/interactionController.ts b/src/smartcontracts/interactionController.ts index f4893bbf..112b5847 100644 --- a/src/smartcontracts/interactionController.ts +++ b/src/smartcontracts/interactionController.ts @@ -2,7 +2,7 @@ import { IProvider } from "../interface"; import { Interaction } from "./interaction"; import { Transaction } from "../transaction"; import { TransactionOnNetwork } from "../transactionOnNetwork"; -import { ContractOutcomeBundle, IInteractionChecker, IResultsParser } from "./interface"; +import { TypedOutcomeBundle, IInteractionChecker, IResultsParser } from "./interface"; import { ContractFunction } from "./function"; import { ResultsParser } from "./resultsParser"; import { InteractionChecker, NullInteractionChecker } from "./interactionChecker"; @@ -44,7 +44,7 @@ export class InteractionController { * @param interaction The interaction used to build the {@link signedInteractionTransaction} * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand */ - async execute(interaction: Interaction, signedInteractionTransaction: Transaction): Promise<{ transaction: TransactionOnNetwork, bundle: ContractOutcomeBundle }> { + async execute(interaction: Interaction, signedInteractionTransaction: Transaction): Promise<{ transaction: TransactionOnNetwork, bundle: TypedOutcomeBundle }> { Logger.info(`InteractionController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${signedInteractionTransaction.getHash()}`) let endpoint = this.getEndpoint(interaction); @@ -61,7 +61,7 @@ export class InteractionController { return { transaction: transactionOnNetwork, bundle: outcomeBundle }; } - async query(interaction: Interaction): Promise { + async query(interaction: Interaction): Promise { let endpoint = this.getEndpoint(interaction); this.checker.checkInteraction(interaction, endpoint); diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index f92d1f99..09d206c4 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -67,7 +67,7 @@ export interface QueryArguments { caller?: Address } -export interface ContractOutcomeBundle { +export interface TypedOutcomeBundle { returnCode: ReturnCode; returnMessage: string; values: TypedValue[]; @@ -76,11 +76,20 @@ export interface ContractOutcomeBundle { thirdValue?: TypedValue; } +export interface UntypedOutcomeBundle { + returnCode: ReturnCode; + returnMessage: string; + values: Buffer[]; +} + export interface IInteractionChecker { checkInteraction(interaction: Interaction, definition: EndpointDefinition): void; } export interface IResultsParser { - parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): ContractOutcomeBundle; - parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): ContractOutcomeBundle + parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): TypedOutcomeBundle; + parseUntypedQueryResponse(queryResponse: QueryResponse): UntypedOutcomeBundle; + + parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): TypedOutcomeBundle; + parseUntypedOutcome(transaction: TransactionOnNetwork): UntypedOutcomeBundle; } diff --git a/src/smartcontracts/query.main.net.spec.ts b/src/smartcontracts/query.main.net.spec.ts index 251dfe94..0398df51 100644 --- a/src/smartcontracts/query.main.net.spec.ts +++ b/src/smartcontracts/query.main.net.spec.ts @@ -45,19 +45,15 @@ describe("test queries on mainnet", function () { this.timeout(5000); // First, expect an error (bad arguments): - try { - let kk = await delegationContract.runQuery(provider, { - func: new ContractFunction("getClaimableRewards") - }); + let response = await delegationContract.runQuery(provider, { + func: new ContractFunction("getClaimableRewards") + }); - throw new errors.ErrTest("unexpected"); - } catch (err: any) { - assert.instanceOf(err, errors.ErrContractQuery); - assert.include(err.toString(), "wrong number of arguments"); - } + assert.include(response.returnCode.toString(), "user error"); + assert.include(response.returnMessage, "wrong number of arguments"); // Then do a successful query: - let response = await delegationContract.runQuery(provider, { + response = await delegationContract.runQuery(provider, { func: new ContractFunction("getClaimableRewards"), args: [new AddressValue(new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"))] }); diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index 2ae746e1..aa6628e5 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -1,13 +1,13 @@ import { ErrInvariantFailed } from "../errors"; import { TransactionOnNetwork } from "../transactionOnNetwork"; import { ArgSerializer } from "./argSerializer"; -import { ContractOutcomeBundle, IResultsParser } from "./interface"; +import { TypedOutcomeBundle, IResultsParser, UntypedOutcomeBundle } from "./interface"; import { QueryResponse } from "./queryResponse"; import { ReturnCode } from "./returnCode"; import { EndpointDefinition } from "./typesystem"; export class ResultsParser implements IResultsParser { - parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): ContractOutcomeBundle { + parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): TypedOutcomeBundle { let parts = queryResponse.getReturnDataParts(); let values = new ArgSerializer().buffersToValues(parts, endpoint.output); @@ -21,13 +21,36 @@ export class ResultsParser implements IResultsParser { }; } + parseUntypedQueryResponse(queryResponse: QueryResponse): UntypedOutcomeBundle { + return { + returnCode: queryResponse.returnCode, + returnMessage: queryResponse.returnMessage, + values: queryResponse.getReturnDataParts() + }; + } + /** * TODO: Improve this function. Currently, the implementation makes some (possibly incorrect) assumptions on the SCR & logs construction logic. * * @param transaction The transaction holding the contract execution outcome (results and logs). * @param endpoint The endpoint definition (ABI) */ - parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): ContractOutcomeBundle { + parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): TypedOutcomeBundle { + let untypedBundle = this.parseUntypedOutcome(transaction); + let values = new ArgSerializer().buffersToValues(untypedBundle.values, endpoint.output); + + return { + returnCode: untypedBundle.returnCode, + returnMessage: untypedBundle.returnMessage, + values: values, + firstValue: values[0], + secondValue: values[1], + thirdValue: values[2] + }; + } + + // TODO: Handle code duplication. + parseUntypedOutcome(transaction: TransactionOnNetwork): UntypedOutcomeBundle { let resultItems = transaction.results.getAll(); // TODO: Fix! The filtering condition below IS NOT necessarily CORRECT (not sufficient). let resultItemWithReturnData = resultItems.find(item => item.nonce.valueOf() != 0); @@ -41,6 +64,8 @@ export class ResultsParser implements IResultsParser { returnCode = ReturnCode.UserError; } + // TODO: Also handle "too much gas provided" (writeLog event) - in this case, the returnData (from the contract) is held in the event.data field. + return { returnCode: returnCode, returnMessage: returnCode.toString(), @@ -62,15 +87,11 @@ export class ResultsParser implements IResultsParser { } let returnCode = ReturnCode.fromBuffer(returnCodePart); - let values = new ArgSerializer().buffersToValues(returnDataParts, endpoint.output); return { returnCode: returnCode, returnMessage: returnCode.toString(), - values: values, - firstValue: values[0], - secondValue: values[1], - thirdValue: values[2] + values: returnDataParts }; } } diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 344ec485..852e9be6 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -7,15 +7,17 @@ import { loadTestWallets, TestWallet } from "../testutils/wallets"; import { loadContractCode } from "../testutils"; import { Logger } from "../logger"; import { assert } from "chai"; -import { Balance } from "../balance"; import { AddressValue, BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { decodeUnsignedNumber } from "./codec"; import { BytesValue } from "./typesystem/bytes"; import { chooseProxyProvider } from "../interactive"; +import { ResultsParser } from "./resultsParser"; describe("test on local testnet", function () { let provider = chooseProxyProvider("local-testnet"); let alice: TestWallet, bob: TestWallet, carol: TestWallet; + let resultsParser = new ResultsParser(); + before(async function () { ({ alice, bob, carol } = await loadTestWallets()); }); @@ -74,7 +76,14 @@ describe("test on local testnet", function () { await transactionIncrement.send(provider); await transactionDeploy.awaitExecuted(provider); + let transactionOnNetwork = await transactionDeploy.getAsOnNetwork(provider); + let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + await transactionIncrement.awaitExecuted(provider); + transactionOnNetwork = await transactionDeploy.getAsOnNetwork(provider); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); // Simulate Logger.trace(JSON.stringify(await simulateOne.simulate(provider), null, 4)); @@ -230,7 +239,7 @@ describe("test on local testnet", function () { let contract = new SmartContract({}); let transactionDeploy = contract.deploy({ code: await loadContractCode("src/testdata/lottery-esdt.wasm"), - gasLimit: new GasLimit(100000000), + gasLimit: new GasLimit(50000000), initArguments: [] }); @@ -243,7 +252,7 @@ describe("test on local testnet", function () { // Start let transactionStart = contract.call({ func: new ContractFunction("start"), - gasLimit: new GasLimit(50000000), + gasLimit: new GasLimit(10000000), args: [ BytesValue.fromUTF8("lucky"), new TokenIdentifierValue(Buffer.from("EGLD")), @@ -270,11 +279,13 @@ describe("test on local testnet", function () { await transactionStart.awaitNotarized(provider); // Let's check the SCRs - let deployResults = (await transactionDeploy.getAsOnNetwork(provider)).results; - // TODO: deployResults.getImmediate().assertSuccess(); + let transactionOnNetwork = await transactionDeploy.getAsOnNetwork(provider); + let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); - let startResults = (await transactionStart.getAsOnNetwork(provider)).results; - // TODO: startResults.getImmediate().assertSuccess(); + transactionOnNetwork = await transactionStart.getAsOnNetwork(provider); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); // Query state, do some assertions let queryResponse = await contract.runQuery(provider, { diff --git a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts index a4b8a3cc..2c1122d9 100644 --- a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts @@ -48,9 +48,9 @@ describe("test smart contract wrapper", function () { let lottery = await erdSys.loadWrapper("src/testdata", "lottery-esdt"); - await lottery.sender(alice).gas(100_000_000).call.deploy(); + await lottery.sender(alice).gas(50_000_000).call.deploy(); - lottery.gas(50_000_000); + lottery.gas(5_000_000); await lottery.call.start("lucky", "EGLD", 1, null, null, 1, null, null); let status = await lottery.query.status("lucky"); diff --git a/src/transactionLogs.ts b/src/transactionLogs.ts index 7c55831b..aad92869 100644 --- a/src/transactionLogs.ts +++ b/src/transactionLogs.ts @@ -20,14 +20,19 @@ export class TransactionLogs { return new TransactionLogs(address, events); } - findEventByIdentifier(identifier: string) { - let event = this.events.filter(event => event.identifier == identifier)[0]; + requireEventByIdentifier(identifier: string) { + let event = this.findEventByIdentifier(identifier); if (event) { return event; } throw new ErrTransactionEventNotFound(identifier); } + + findEventByIdentifier(identifier: string) { + let event = this.events.filter(event => event.identifier == identifier)[0]; + return event; + } } export class TransactionEvent { From 6caabcee7341c006247ae9f2957932d603b0b80e Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 20:29:21 +0200 Subject: [PATCH 13/37] Improve tests, fix readme. --- README.md | 6 +++--- .../smartContractResults.local.net.spec.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 49e6b23b..8d508ed5 100644 --- a/README.md +++ b/README.md @@ -18,17 +18,17 @@ Elrond SDK for JavaScript and TypeScript (written in TypeScript). The most comprehensive usage examples are captured within the unit and the integration tests. Specifically, in the `*.spec.ts` files of the source code. For example: - - [transaction.dev.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/transaction.dev.net.spec.ts) + - [transaction.local.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/transaction.local.net.spec.ts) - [address.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/address.spec.ts) - [transactionPayloadBuilders.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/transactionPayloadBuilders.spec.ts) - [smartContract.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/smartContract.spec.ts) - - [smartContract.dev.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/smartContract.dev.net.spec.ts) + - [smartContract.local.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/smartContract.local.net.spec.ts) - [query.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/query.spec.ts) - [query.main.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/query.main.net.spec.ts) For advanced smart contract interaction, using ABIs, please see the following test files: - - [interaction.dev.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/interaction.dev.net.spec.ts) + - [interaction.local.net.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/interaction.local.net.spec.ts) - [abiRegistry.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/typesystem/abiRegistry.spec.ts) - [argSerializer.spec.ts](https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/argSerializer.spec.ts) diff --git a/src/smartcontracts/smartContractResults.local.net.spec.ts b/src/smartcontracts/smartContractResults.local.net.spec.ts index 37e3bb61..57505655 100644 --- a/src/smartcontracts/smartContractResults.local.net.spec.ts +++ b/src/smartcontracts/smartContractResults.local.net.spec.ts @@ -6,10 +6,13 @@ import { assert } from "chai"; import { chooseProxyProvider } from "../interactive"; import { SmartContract } from "./smartContract"; import { ContractFunction } from "./function"; +import { ResultsParser } from "./resultsParser"; describe("fetch transactions from local testnet", function () { let provider = chooseProxyProvider("local-testnet");; let alice: TestWallet; + let resultsParser = new ResultsParser(); + before(async function () { ({ alice } = await loadTestWallets()); }); @@ -56,7 +59,13 @@ describe("fetch transactions from local testnet", function () { await transactionDeploy.getAsOnNetwork(provider); await transactionIncrement.getAsOnNetwork(provider); - // TODO: Assert success in SCRs. + let transactionOnNetwork = transactionDeploy.getAsOnNetworkCached(); + let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + transactionOnNetwork = transactionIncrement.getAsOnNetworkCached(); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); }); it("ESDT", async function () { From 95e4bf4faca74f57c592e4c03803c4e74a604d04 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 20:29:50 +0200 Subject: [PATCH 14/37] 10.0.0-alpha.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index af7bfb4c..5b562323 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "9.2.2", + "version": "10.0.0-alpha.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ee0663a2..49d2609d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "9.2.2", + "version": "10.0.0-alpha.0", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", From 646206ecfa44b6f8c7029b8d7b8a72b06bd59933 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 20:30:27 +0200 Subject: [PATCH 15/37] 11.0.0-alpha.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b562323..838307c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.0", + "version": "11.0.0-alpha.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 49d2609d..d47c30f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.0", + "version": "11.0.0-alpha.0", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", From c408dfcd21da94b14d262814669c656b7635175f Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 20:32:47 +0200 Subject: [PATCH 16/37] Fix version (pre-major release). --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 838307c7..5b562323 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "11.0.0-alpha.0", + "version": "10.0.0-alpha.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d47c30f6..49d2609d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "11.0.0-alpha.0", + "version": "10.0.0-alpha.0", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", From 0af45d6132baa5469924c7543c96d4d35d2aad2a Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 20:35:34 +0200 Subject: [PATCH 17/37] Adjust publish workflow (will be undo before review / merge). --- .github/workflows/erdjs-publish.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/erdjs-publish.yml b/.github/workflows/erdjs-publish.yml index 4223a85a..77aa6306 100644 --- a/.github/workflows/erdjs-publish.yml +++ b/.github/workflows/erdjs-publish.yml @@ -13,21 +13,10 @@ jobs: node-version: 12 registry-url: https://registry.npmjs.org/ - - name: Create release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo " - [npmjs](https://www.npmjs.com/package/@elrondnetwork/erdjs)" >> notes.txt - echo " - [CHANGELOG](https://github.com/ElrondNetwork/elrond-sdk-erdjs/blob/main/CHANGELOG.md)" >> notes.txt - echo "" >> notes.txt - - RELEASE_TAG=v$(node -p "require('./package.json').version") - gh release create $RELEASE_TAG --title="$RELEASE_TAG" --generate-notes --notes-file=notes.txt - - run: npm ci - run: npm test - name: Publish to npmjs env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - run: npm publish --access=public + run: npm publish --access=public --tag=alpha From 9ef253f41c847f8a5e8bc9d7232686a8b2b78558 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 22:24:22 +0200 Subject: [PATCH 18/37] Refactor / rename. --- src/smartcontracts/index.ts | 2 +- .../interaction.local.net.spec.ts | 8 +- src/smartcontracts/interaction.spec.ts | 8 +- src/smartcontracts/interactionController.ts | 91 --------------- src/smartcontracts/interface.ts | 6 + src/smartcontracts/smartContractController.ts | 106 ++++++++++++++++++ 6 files changed, 121 insertions(+), 100 deletions(-) delete mode 100644 src/smartcontracts/interactionController.ts create mode 100644 src/smartcontracts/smartContractController.ts diff --git a/src/smartcontracts/index.ts b/src/smartcontracts/index.ts index e383e1cf..138f5115 100644 --- a/src/smartcontracts/index.ts +++ b/src/smartcontracts/index.ts @@ -8,7 +8,7 @@ export * from "./argSerializer"; export * from "./code"; export * from "./codec"; export * from "./codeMetadata"; -export * from "./interactionController"; +export * from "./smartContractController"; export * from "./function"; export * from "./interaction"; export * from "./interface"; diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index d426ab2d..3ea70255 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -1,4 +1,4 @@ -import { DefaultInteractionController } from "./interactionController"; +import { DefaultSmartContractController } from "./smartContractController"; import { SmartContract } from "./smartContract"; import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, TypedValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadContractCode, loadTestWallets, TestWallet } from "../testutils"; @@ -26,7 +26,7 @@ describe("test smart contract interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["answer"]); let contract = new SmartContract({ abi: abi }); - let controller = new DefaultInteractionController(abi, provider); + let controller = new DefaultSmartContractController(abi, provider); // Currently, this has to be called before creating any Interaction objects, // because the Transaction objects created under the hood point to the "default" NetworkConfig. @@ -62,7 +62,7 @@ describe("test smart contract interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/counter.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["counter"]); let contract = new SmartContract({ abi: abi }); - let controller = new DefaultInteractionController(abi, provider); + let controller = new DefaultSmartContractController(abi, provider); // Currently, this has to be called before creating any Interaction objects, // because the Transaction objects created under the hood point to the "default" NetworkConfig. @@ -101,7 +101,7 @@ describe("test smart contract interactor", function () { let abiRegistry = await loadAbiRegistry(["src/testdata/lottery-esdt.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ abi: abi }); - let controller = new DefaultInteractionController(abi, provider); + let controller = new DefaultSmartContractController(abi, provider); // Currently, this has to be called before creating any Interaction objects, // because the Transaction objects created under the hood point to the "default" NetworkConfig. diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index d1f3e764..2c823473 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -1,4 +1,4 @@ -import { DefaultInteractionController } from "./interactionController"; +import { DefaultSmartContractController } from "./smartContractController"; import { SmartContract } from "./smartContract"; import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { @@ -112,7 +112,7 @@ describe("test smart contract interactor", function() { let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["answer"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let controller = new DefaultInteractionController(abi, provider); + let controller = new DefaultSmartContractController(abi, provider); let interaction = contract.methods .getUltimateAnswer() @@ -173,7 +173,7 @@ describe("test smart contract interactor", function() { let abiRegistry = await loadAbiRegistry(["src/testdata/counter.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["counter"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let controller = new DefaultInteractionController(abi, provider); + let controller = new DefaultSmartContractController(abi, provider); let getInteraction = contract.methods.get(); let incrementInteraction = (contract.methods.increment()).withGasLimit(new GasLimit(543210)); @@ -220,7 +220,7 @@ describe("test smart contract interactor", function() { let abiRegistry = await loadAbiRegistry(["src/testdata/lottery-esdt.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); let contract = new SmartContract({ address: dummyAddress, abi: abi }); - let controller = new DefaultInteractionController(abi, provider); + let controller = new DefaultSmartContractController(abi, provider); let startInteraction = ( contract.methods diff --git a/src/smartcontracts/interactionController.ts b/src/smartcontracts/interactionController.ts deleted file mode 100644 index 112b5847..00000000 --- a/src/smartcontracts/interactionController.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { IProvider } from "../interface"; -import { Interaction } from "./interaction"; -import { Transaction } from "../transaction"; -import { TransactionOnNetwork } from "../transactionOnNetwork"; -import { TypedOutcomeBundle, IInteractionChecker, IResultsParser } from "./interface"; -import { ContractFunction } from "./function"; -import { ResultsParser } from "./resultsParser"; -import { InteractionChecker, NullInteractionChecker } from "./interactionChecker"; -import { EndpointDefinition } from "./typesystem"; -import { Logger } from "../logger"; - -/** - * Internal interface: the smart contract ABI, as seen from the perspective of an {@link InteractionController}. - */ -interface ISmartContractAbi { - getEndpoint(func: ContractFunction): EndpointDefinition; -} - -/** - * An interaction controller, suitable for frontends and dApp, - * where signing is performed by means of an external wallet provider. - */ -export class InteractionController { - private readonly abi: ISmartContractAbi; - private readonly checker: IInteractionChecker; - private readonly parser: IResultsParser; - private readonly provider: IProvider; - - constructor( - abi: ISmartContractAbi, - checker: IInteractionChecker, - parser: IResultsParser, - provider: IProvider, - ) { - this.abi = abi; - this.checker = checker; - this.parser = parser; - this.provider = provider; - } - - /** - * Broadcasts an alredy-signed interaction transaction, and also waits for its execution on the Network. - * - * @param interaction The interaction used to build the {@link signedInteractionTransaction} - * @param signedInteractionTransaction The interaction transaction, which must be signed beforehand - */ - async execute(interaction: Interaction, signedInteractionTransaction: Transaction): Promise<{ transaction: TransactionOnNetwork, bundle: TypedOutcomeBundle }> { - Logger.info(`InteractionController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${signedInteractionTransaction.getHash()}`) - - let endpoint = this.getEndpoint(interaction); - - this.checker.checkInteraction(interaction, endpoint); - - await signedInteractionTransaction.send(this.provider); - await signedInteractionTransaction.awaitExecuted(this.provider); - let transactionOnNetwork = await signedInteractionTransaction.getAsOnNetwork(this.provider); - let outcomeBundle = this.parser.parseOutcome(transactionOnNetwork, endpoint); - - Logger.info(`InteractionController.execute [end]: function = ${interaction.getFunction()}, transaction = ${signedInteractionTransaction.getHash()}, return code = ${outcomeBundle.returnCode}`) - - return { transaction: transactionOnNetwork, bundle: outcomeBundle }; - } - - async query(interaction: Interaction): Promise { - let endpoint = this.getEndpoint(interaction); - - this.checker.checkInteraction(interaction, endpoint); - - let query = interaction.buildQuery(); - let queryResponse = await this.provider.queryContract(query); - let outcomeBundle = this.parser.parseQueryResponse(queryResponse, endpoint); - return outcomeBundle; - } - - private getEndpoint(interaction: Interaction) { - let func = interaction.getFunction(); - return this.abi.getEndpoint(func); - } -} - -export class DefaultInteractionController extends InteractionController { - constructor(abi: ISmartContractAbi, provider: IProvider) { - super(abi, new InteractionChecker(), new ResultsParser(), provider); - } -} - -export class NoCheckInteractionController extends InteractionController { - constructor(abi: ISmartContractAbi, provider: IProvider) { - super(abi, new NullInteractionChecker(), new ResultsParser(), provider); - } -} diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index 09d206c4..7df4026d 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -82,6 +82,12 @@ export interface UntypedOutcomeBundle { values: Buffer[]; } +export interface ISmartContractController { + deploy(transaction: Transaction): Promise<{ transactionOnNetwork: TransactionOnNetwork, bundle: UntypedOutcomeBundle }>; + execute(interaction: Interaction, transaction: Transaction): Promise<{ transactionOnNetwork: TransactionOnNetwork, bundle: TypedOutcomeBundle }>; + query(interaction: Interaction): Promise; +} + export interface IInteractionChecker { checkInteraction(interaction: Interaction, definition: EndpointDefinition): void; } diff --git a/src/smartcontracts/smartContractController.ts b/src/smartcontracts/smartContractController.ts new file mode 100644 index 00000000..a47e10c2 --- /dev/null +++ b/src/smartcontracts/smartContractController.ts @@ -0,0 +1,106 @@ +import { IProvider } from "../interface"; +import { Interaction } from "./interaction"; +import { Transaction } from "../transaction"; +import { TransactionOnNetwork } from "../transactionOnNetwork"; +import { TypedOutcomeBundle, IInteractionChecker, IResultsParser, ISmartContractController, UntypedOutcomeBundle } from "./interface"; +import { ContractFunction } from "./function"; +import { ResultsParser } from "./resultsParser"; +import { InteractionChecker, NullInteractionChecker } from "./interactionChecker"; +import { EndpointDefinition } from "./typesystem"; +import { Logger } from "../logger"; + +/** + * Internal interface: the smart contract ABI, as seen from the perspective of an {@link SmartContractController}. + */ +interface ISmartContractAbi { + getEndpoint(func: ContractFunction): EndpointDefinition; +} + +/** + * A (frontend) controller, suitable for frontends and dApp, + * where signing is performed by means of an external wallet provider. + */ +export class SmartContractController implements ISmartContractController { + private readonly abi: ISmartContractAbi; + private readonly checker: IInteractionChecker; + private readonly parser: IResultsParser; + private readonly provider: IProvider; + + constructor( + abi: ISmartContractAbi, + checker: IInteractionChecker, + parser: IResultsParser, + provider: IProvider, + ) { + this.abi = abi; + this.checker = checker; + this.parser = parser; + this.provider = provider; + } + + async deploy(transaction: Transaction): Promise<{ transactionOnNetwork: TransactionOnNetwork, bundle: UntypedOutcomeBundle }> { + Logger.info(`SmartContractController.deploy [begin]: transaction = ${transaction.getHash()}`); + + await transaction.send(this.provider); + await transaction.awaitExecuted(this.provider); + let transactionOnNetwork = await transaction.getAsOnNetwork(this.provider); + let bundle = this.parser.parseUntypedOutcome(transactionOnNetwork); + + Logger.info(`SmartContractController.deploy [end]: transaction = ${transaction.getHash()}, return code = ${bundle.returnCode}`); + return { transactionOnNetwork, bundle }; + } + + /** + * Broadcasts an alredy-signed interaction transaction, and also waits for its execution on the Network. + * + * @param interaction The interaction used to build the {@link transaction} + * @param transaction The interaction transaction, which must be signed beforehand + */ + async execute(interaction: Interaction, transaction: Transaction): Promise<{ transactionOnNetwork: TransactionOnNetwork, bundle: TypedOutcomeBundle }> { + Logger.info(`SmartContractController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${transaction.getHash()}`); + + let endpoint = this.getEndpoint(interaction); + + this.checker.checkInteraction(interaction, endpoint); + + await transaction.send(this.provider); + await transaction.awaitExecuted(this.provider); + let transactionOnNetwork = await transaction.getAsOnNetwork(this.provider); + let bundle = this.parser.parseOutcome(transactionOnNetwork, endpoint); + + Logger.info(`SmartContractController.execute [end]: function = ${interaction.getFunction()}, transaction = ${transaction.getHash()}, return code = ${bundle.returnCode}`); + return { transactionOnNetwork, bundle }; + } + + async query(interaction: Interaction): Promise { + Logger.debug(`SmartContractController.query [begin]: function = ${interaction.getFunction()}`); + + let endpoint = this.getEndpoint(interaction); + + this.checker.checkInteraction(interaction, endpoint); + + let query = interaction.buildQuery(); + let queryResponse = await this.provider.queryContract(query); + let bundle = this.parser.parseQueryResponse(queryResponse, endpoint); + + Logger.debug(`SmartContractController.query [end]: function = ${interaction.getFunction()}, return code = ${bundle.returnCode}`); + return bundle; + } + + private getEndpoint(interaction: Interaction) { + let func = interaction.getFunction(); + return this.abi.getEndpoint(func); + } +} + +export class DefaultSmartContractController extends SmartContractController { + constructor(abi: ISmartContractAbi, provider: IProvider) { + super(abi, new InteractionChecker(), new ResultsParser(), provider); + } +} + +export class NoCheckSmartContractController extends SmartContractController { + constructor(abi: ISmartContractAbi, provider: IProvider) { + super(abi, new NullInteractionChecker(), new ResultsParser(), provider); + } +} From 17d122064071106b58bc5ad33c0781d06ba853de Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Sun, 20 Mar 2022 22:24:47 +0200 Subject: [PATCH 19/37] Bump version. --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b562323..39b1f67d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.0", + "version": "10.0.0-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 49d2609d..e784ea29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.0", + "version": "10.0.0-alpha.1", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", From 56748936429321d2a4ec981a09153133012c2b73 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 21 Mar 2022 08:53:07 +0200 Subject: [PATCH 20/37] Handle SCDeploy, as well. --- src/smartcontracts/resultsParser.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index aa6628e5..6ccfd53c 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -60,6 +60,9 @@ export class ResultsParser implements IResultsParser { if (transaction.logs.findEventByIdentifier("completedTxEvent")) { returnCode = ReturnCode.Ok; + } else if (transaction.logs.findEventByIdentifier("SCDeploy")) { + // TODO: Check if this logic is correct. + returnCode = ReturnCode.Ok; } else if (transaction.logs.findEventByIdentifier("signalError")) { returnCode = ReturnCode.UserError; } From 4d7ed9cc0cd6050ea7b2f09f26c2170bb0135d26 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 21 Mar 2022 08:53:40 +0200 Subject: [PATCH 21/37] Bump alpha version. --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 39b1f67d..bee73212 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.1", + "version": "10.0.0-alpha.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e784ea29..ab8bc7b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.1", + "version": "10.0.0-alpha.2", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", From a721cc47d6b7476494f8b0394337f34e5ccc83c6 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 21 Mar 2022 22:31:48 +0200 Subject: [PATCH 22/37] TokenIdentifierValue: constructed from string, valueOf() is string. --- src/smartcontracts/codec/binary.spec.ts | 9 ++++----- src/smartcontracts/codec/tokenIdentifier.ts | 8 ++++---- src/smartcontracts/interaction.local.net.spec.ts | 4 ++-- src/smartcontracts/interaction.spec.ts | 4 ++-- src/smartcontracts/interactionChecker.spec.ts | 2 +- src/smartcontracts/nativeSerializer.ts | 10 +++++----- src/smartcontracts/smartContract.local.net.spec.ts | 2 +- src/smartcontracts/typesystem/bytes.ts | 4 ++++ src/smartcontracts/typesystem/tokenIdentifier.ts | 8 ++++---- .../wrapper/contractWrapper.local.net.spec.ts | 2 +- src/smartcontracts/wrapper/contractWrapper.spec.ts | 2 +- src/smartcontracts/wrapper/esdt.spec.ts | 4 ++-- 12 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/smartcontracts/codec/binary.spec.ts b/src/smartcontracts/codec/binary.spec.ts index 34401061..90cefd24 100644 --- a/src/smartcontracts/codec/binary.spec.ts +++ b/src/smartcontracts/codec/binary.spec.ts @@ -3,7 +3,6 @@ import { BinaryCodec, BinaryCodecConstraints } from "./binary"; import { AddressType, AddressValue, BigIntType, BigUIntType, BigUIntValue, BooleanType, BooleanValue, I16Type, I32Type, I64Type, I8Type, NumericalType, NumericalValue, Struct, Field, StructType, TypedValue, U16Type, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, List, ListType, EnumType, EnumVariantDefinition, EnumValue, ArrayVec, ArrayVecType, U16Value, TokenIdentifierType, TokenIdentifierValue, StringValue, StringType } from "../typesystem"; import { discardSuperfluousBytesInTwosComplement, discardSuperfluousZeroBytes, isMsbOne } from "./utils"; import { Address } from "../../address"; -import { Balance } from "../../balance"; import { BytesType, BytesValue } from "../typesystem/bytes"; import BigNumber from "bignumber.js"; import { FieldDefinition } from "../typesystem/fields"; @@ -200,7 +199,7 @@ describe("test binary codec (advanced)", () => { ); let fooStruct = new Struct(fooType, [ - new Field(new TokenIdentifierValue(Buffer.from("lucky-token")), "token_identifier"), + new Field(new TokenIdentifierValue("lucky-token"), "token_identifier"), new Field(new BigUIntValue(1), "ticket_price"), new Field(new U32Value(0), "tickets_left"), new Field(new U64Value(new BigNumber("0x000000005fc2b9db")), "deadline"), @@ -219,7 +218,7 @@ describe("test binary codec (advanced)", () => { let plainFoo = decoded.valueOf(); assert.deepEqual(plainFoo, { - token_identifier: Buffer.from("lucky-token"), + token_identifier: "lucky-token", ticket_price: new BigNumber("1"), tickets_left: new BigNumber(0), deadline: new BigNumber("0x000000005fc2b9db", 16), @@ -241,7 +240,7 @@ describe("test binary codec (advanced)", () => { ); let paymentStruct = new Struct(paymentType, [ - new Field(new TokenIdentifierValue(Buffer.from("TEST-1234")), "token_identifier"), + new Field(new TokenIdentifierValue("TEST-1234"), "token_identifier"), new Field(new U64Value(new BigNumber(42)), "nonce"), new Field(new BigUIntValue(new BigNumber("123450000000000000000")), "amount") ]); @@ -256,7 +255,7 @@ describe("test binary codec (advanced)", () => { let decodedPayment = decoded.valueOf(); assert.deepEqual(decodedPayment, { - token_identifier: Buffer.from("TEST-1234"), + token_identifier: "TEST-1234", nonce: new BigNumber(42), amount: new BigNumber("123450000000000000000"), }); diff --git a/src/smartcontracts/codec/tokenIdentifier.ts b/src/smartcontracts/codec/tokenIdentifier.ts index da005943..ea28d321 100644 --- a/src/smartcontracts/codec/tokenIdentifier.ts +++ b/src/smartcontracts/codec/tokenIdentifier.ts @@ -7,20 +7,20 @@ export class TokenIdentifierCodec { decodeNested(buffer: Buffer): [TokenIdentifierValue, number] { let [bytesValue, length] = this.bytesCodec.decodeNested(buffer); - return [new TokenIdentifierValue(bytesValue.valueOf()), length]; + return [new TokenIdentifierValue(bytesValue.toString()), length]; } decodeTopLevel(buffer: Buffer): TokenIdentifierValue { let bytesValue = this.bytesCodec.decodeTopLevel(buffer); - return new TokenIdentifierValue(bytesValue.valueOf()); + return new TokenIdentifierValue(bytesValue.toString()); } encodeNested(tokenIdentifier: TokenIdentifierValue): Buffer { - let bytesValue = new BytesValue(tokenIdentifier.valueOf()); + let bytesValue = BytesValue.fromUTF8(tokenIdentifier.valueOf()); return this.bytesCodec.encodeNested(bytesValue); } encodeTopLevel(tokenIdentifier: TokenIdentifierValue): Buffer { - return tokenIdentifier.valueOf(); + return Buffer.from(tokenIdentifier.valueOf()); } } diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 3ea70255..f246d714 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -111,7 +111,7 @@ describe("test smart contract interactor", function () { let startInteraction = contract.methods.start([ BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue(Buffer.from("EGLD")), + new TokenIdentifierValue("EGLD"), new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newMissing(), @@ -156,7 +156,7 @@ describe("test smart contract interactor", function () { delete info.deadline; assert.deepEqual(info, { - token_identifier: Buffer.from("EGLD"), + token_identifier: "EGLD", ticket_price: new BigNumber("1"), tickets_left: new BigNumber(800), max_entries_per_user: new BigNumber(1), diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 2c823473..5c58d588 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -226,7 +226,7 @@ describe("test smart contract interactor", function() { contract.methods .start([ BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue(Buffer.from("lucky-token")), + new TokenIdentifierValue("lucky-token"), new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newMissing(), @@ -278,7 +278,7 @@ describe("test smart contract interactor", function() { assert.lengthOf(infoReturnValues, 1); assert.deepEqual(infoFirstValue!.valueOf(), { - token_identifier: Buffer.from("lucky-token"), + token_identifier: "lucky-token", ticket_price: new BigNumber("1"), tickets_left: new BigNumber(0), deadline: new BigNumber("0x000000005fc2b9db", 16), diff --git a/src/smartcontracts/interactionChecker.spec.ts b/src/smartcontracts/interactionChecker.spec.ts index 6c7cb98c..46c44826 100644 --- a/src/smartcontracts/interactionChecker.spec.ts +++ b/src/smartcontracts/interactionChecker.spec.ts @@ -54,7 +54,7 @@ describe("integration tests: test checker within interactor", function () { assert.throw(() => { let interaction = contract.methods.start([ BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue(Buffer.from("lucky-token")), + new TokenIdentifierValue("lucky-token"), new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newProvided(new U32Value(1)), diff --git a/src/smartcontracts/nativeSerializer.ts b/src/smartcontracts/nativeSerializer.ts index 75db8c20..25df6dd3 100644 --- a/src/smartcontracts/nativeSerializer.ts +++ b/src/smartcontracts/nativeSerializer.ts @@ -193,7 +193,7 @@ export namespace NativeSerializer { return new BooleanValue(native); } if (type instanceof TokenIdentifierType) { - return new TokenIdentifierValue(convertNativeToBuffer(native, errorContext)); + return new TokenIdentifierValue(convertNativeToString(native, errorContext)); } errorContext.throwError(`(function: toPrimitive) unsupported type ${type}`); } @@ -217,18 +217,18 @@ export namespace NativeSerializer { errorContext.convertError(native, "BytesValue"); } - function convertNativeToBuffer(native: NativeTypes.NativeBuffer, errorContext: ArgumentErrorContext): Buffer { + function convertNativeToString(native: NativeTypes.NativeBuffer, errorContext: ArgumentErrorContext): string { if (native === undefined) { errorContext.convertError(native, "Buffer"); } if (native instanceof Buffer) { - return native; + return native.toString(); } if (typeof native === "string") { - return Buffer.from(native); + return native; } if (((native).getTokenIdentifier)) { - return Buffer.from(native.getTokenIdentifier()); + return native.getTokenIdentifier(); } errorContext.convertError(native, "Buffer"); } diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 852e9be6..e220124f 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -255,7 +255,7 @@ describe("test on local testnet", function () { gasLimit: new GasLimit(10000000), args: [ BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue(Buffer.from("EGLD")), + new TokenIdentifierValue("EGLD"), new BigUIntValue(1), OptionValue.newMissing(), OptionValue.newMissing(), diff --git a/src/smartcontracts/typesystem/bytes.ts b/src/smartcontracts/typesystem/bytes.ts index 35b36a60..4907eaad 100644 --- a/src/smartcontracts/typesystem/bytes.ts +++ b/src/smartcontracts/typesystem/bytes.ts @@ -49,4 +49,8 @@ export class BytesValue extends PrimitiveValue { valueOf(): Buffer { return this.value; } + + toString() { + return this.value.toString(); + } } diff --git a/src/smartcontracts/typesystem/tokenIdentifier.ts b/src/smartcontracts/typesystem/tokenIdentifier.ts index 757056f9..ab7ac63a 100644 --- a/src/smartcontracts/typesystem/tokenIdentifier.ts +++ b/src/smartcontracts/typesystem/tokenIdentifier.ts @@ -8,9 +8,9 @@ export class TokenIdentifierType extends PrimitiveType { } export class TokenIdentifierValue extends PrimitiveValue { - private readonly value: Buffer; + private readonly value: string; - constructor(value: Buffer) { + constructor(value: string) { super(new TokenIdentifierType()); this.value = value; } @@ -27,10 +27,10 @@ export class TokenIdentifierValue extends PrimitiveValue { return false; } - return this.value.equals(other.value); + return this.value == other.value; } - valueOf(): Buffer { + valueOf(): string { return this.value; } diff --git a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts index 2c1122d9..7a7fc064 100644 --- a/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.local.net.spec.ts @@ -61,7 +61,7 @@ describe("test smart contract wrapper", function () { delete info.deadline; assert.deepEqual(info, { - token_identifier: Buffer.from("EGLD"), + token_identifier: "EGLD", ticket_price: new BigNumber("1"), tickets_left: new BigNumber(800), max_entries_per_user: new BigNumber(1), diff --git a/src/smartcontracts/wrapper/contractWrapper.spec.ts b/src/smartcontracts/wrapper/contractWrapper.spec.ts index 98c664ec..a0f3c815 100644 --- a/src/smartcontracts/wrapper/contractWrapper.spec.ts +++ b/src/smartcontracts/wrapper/contractWrapper.spec.ts @@ -87,7 +87,7 @@ describe("test smart contract wrapper", async function() { ); assert.deepEqual(info, { - token_identifier: Buffer.from("lucky-token"), + token_identifier: "lucky-token", ticket_price: new BigNumber("1"), tickets_left: new BigNumber(0), deadline: new BigNumber("0x000000005fc2b9db", 16), diff --git a/src/smartcontracts/wrapper/esdt.spec.ts b/src/smartcontracts/wrapper/esdt.spec.ts index 53568ab2..f3cd5a5e 100644 --- a/src/smartcontracts/wrapper/esdt.spec.ts +++ b/src/smartcontracts/wrapper/esdt.spec.ts @@ -73,7 +73,7 @@ describe("test ESDT transfers via the smart contract wrapper", async function () }, auctioned_token: { nonce: new BigNumber(1), - token_type: Buffer.from("TEST-feed60"), + token_type: "TEST-feed60", }, creator_royalties_percentage: new BigNumber(2500), current_bid: new BigNumber(0), @@ -86,7 +86,7 @@ describe("test ESDT transfers via the smart contract wrapper", async function () original_owner: new Address("erd1wuq9x0w2yl7lc96653hw6pltc2ay0f098mxhztn64vh6322ccmussa83g9"), payment_token: { nonce: new BigNumber(0), - token_type: Buffer.from("EGLD") + token_type: "EGLD" }, start_time: new BigNumber(1643744844) } From 395432de72e08ff04c6be99900ce78d8b285967a Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 11:05:28 +0200 Subject: [PATCH 23/37] Define getFieldValue() on structs and enums. --- src/errors.ts | 18 ++++++++ src/smartcontracts/typesystem/enum.spec.ts | 45 ++++++++++++++++++++ src/smartcontracts/typesystem/enum.ts | 12 ++++++ src/smartcontracts/typesystem/struct.spec.ts | 35 +++++++++++++++ src/smartcontracts/typesystem/struct.ts | 18 ++++++-- 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/smartcontracts/typesystem/enum.spec.ts create mode 100644 src/smartcontracts/typesystem/struct.spec.ts diff --git a/src/errors.ts b/src/errors.ts index 5aa28431..f974f084 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -409,6 +409,24 @@ export class ErrTypingSystem extends Err { } } +/** + * Signals a missing field on a struct. + */ + export class ErrMissingFieldOnStruct extends Err { + public constructor(fieldName: string, structName: string) { + super(`field ${fieldName} does not exist on struct ${structName}`); + } +} + +/** + * Signals a missing field on an enum. + */ + export class ErrMissingFieldOnEnum extends Err { + public constructor(fieldName: string, enumName: string) { + super(`field ${fieldName} does not exist on enum ${enumName}`); + } +} + /** * Signals a generic codec (encode / decode) error. */ diff --git a/src/smartcontracts/typesystem/enum.spec.ts b/src/smartcontracts/typesystem/enum.spec.ts new file mode 100644 index 00000000..23876e5c --- /dev/null +++ b/src/smartcontracts/typesystem/enum.spec.ts @@ -0,0 +1,45 @@ +import * as errors from "../../errors"; +import { assert } from "chai"; +import { U8Type, U8Value } from "./numerical"; +import { Field, FieldDefinition } from "./fields"; +import { EnumType, EnumValue, EnumVariantDefinition } from "./enum"; +import { StringType, StringValue } from "./string"; + +describe("test enums", () => { + it("should get fields", () => { + let greenVariant = new EnumVariantDefinition("Green", 0, [ + new FieldDefinition("0", "red component", new U8Type()), + new FieldDefinition("1", "green component", new U8Type()), + new FieldDefinition("2", "blue component", new U8Type()), + ]); + + let orangeVariant = new EnumVariantDefinition("Orange", 1, [ + new FieldDefinition("0", "hex code", new StringType()) + ]); + + let enumType = new EnumType("Colour", [ + greenVariant, + orangeVariant + ]); + + let green = new EnumValue(enumType, greenVariant, [ + new Field(new U8Value(0), "0"), + new Field(new U8Value(255), "1"), + new Field(new U8Value(0), "2") + ]); + + let orange = new EnumValue(enumType, orangeVariant, [ + new Field(new StringValue("#FFA500"), "0") + ]); + + assert.lengthOf(green.getFields(), 3); + assert.lengthOf(orange.getFields(), 1); + assert.deepEqual(green.getFieldValue("0").toNumber(), 0); + assert.deepEqual(green.getFieldValue("1").toNumber(), 255); + assert.deepEqual(green.getFieldValue("2").toNumber(), 0); + assert.deepEqual(orange.getFieldValue("0"), "#FFA500"); + assert.throw(() => green.getFieldValue("3"), errors.ErrMissingFieldOnEnum); + assert.throw(() => orange.getFieldValue("1"), errors.ErrMissingFieldOnEnum); + }); +}); + diff --git a/src/smartcontracts/typesystem/enum.ts b/src/smartcontracts/typesystem/enum.ts index 1d1e20b1..c924d604 100644 --- a/src/smartcontracts/typesystem/enum.ts +++ b/src/smartcontracts/typesystem/enum.ts @@ -1,3 +1,4 @@ +import { ErrMissingFieldOnEnum } from "../../errors"; import { guardTrue, guardValueIsSet } from "../../utils"; import { Field, FieldDefinition, Fields } from "./fields"; import { CustomType, TypedValue } from "./types"; @@ -60,12 +61,14 @@ export class EnumValue extends TypedValue { readonly name: string; readonly discriminant: number; private readonly fields: Field[] = []; + private readonly fieldsByName: Map; constructor(type: EnumType, variant: EnumVariantDefinition, fields: Field[]) { super(type); this.name = variant.name; this.discriminant = variant.discriminant; this.fields = fields; + this.fieldsByName = new Map(fields.map(field => [field.name, field])); let definitions = variant.getFieldsDefinitions(); Fields.checkTyping(this.fields, definitions); @@ -106,6 +109,15 @@ export class EnumValue extends TypedValue { return this.fields; } + getFieldValue(name: string): any { + let field = this.fieldsByName.get(name); + if (field) { + return field.value.valueOf(); + } + + throw new ErrMissingFieldOnEnum(name, this.getType().getName()); + } + valueOf() { let result: any = { name: this.name, fields: [] }; diff --git a/src/smartcontracts/typesystem/struct.spec.ts b/src/smartcontracts/typesystem/struct.spec.ts new file mode 100644 index 00000000..ae763996 --- /dev/null +++ b/src/smartcontracts/typesystem/struct.spec.ts @@ -0,0 +1,35 @@ +import * as errors from "../../errors"; +import { assert } from "chai"; +import { BigUIntType, BigUIntValue, U32Type, U32Value } from "./numerical"; +import { BytesType, BytesValue } from "./bytes"; +import { Struct, StructType } from "./struct"; +import { Field, FieldDefinition } from "./fields"; +import { TokenIdentifierType, TokenIdentifierValue } from "./tokenIdentifier"; + +describe("test structs", () => { + it("should get fields", () => { + let fooType = new StructType( + "Foo", + [ + new FieldDefinition("a", "", new TokenIdentifierType()), + new FieldDefinition("b", "", new BigUIntType()), + new FieldDefinition("c", "", new U32Type()), + new FieldDefinition("d", "", new BytesType()), + ] + ); + + let fooStruct = new Struct(fooType, [ + new Field(new TokenIdentifierValue("lucky-token"), "a"), + new Field(new BigUIntValue(1), "b"), + new Field(new U32Value(42), "c"), + new Field(new BytesValue(Buffer.from([0x64])), "d"), + ]); + + assert.lengthOf(fooStruct.getFields(), 4); + assert.deepEqual(fooStruct.getFieldValue("a"), "lucky-token"); + assert.deepEqual(fooStruct.getFieldValue("b").toNumber(), 1); + assert.deepEqual(fooStruct.getFieldValue("c").toNumber(), 42); + assert.deepEqual(fooStruct.getFieldValue("d"), Buffer.from([0x64])); + assert.throw(() => fooStruct.getFieldValue("e"), errors.ErrMissingFieldOnStruct); + }); +}); diff --git a/src/smartcontracts/typesystem/struct.ts b/src/smartcontracts/typesystem/struct.ts index b1eec34b..feab5e79 100644 --- a/src/smartcontracts/typesystem/struct.ts +++ b/src/smartcontracts/typesystem/struct.ts @@ -1,3 +1,4 @@ +import { ErrMissingFieldOnStruct, ErrTypingSystem } from "../../errors"; import { FieldDefinition, Field, Fields } from "./fields"; import { CustomType, TypedValue } from "./types"; @@ -19,17 +20,17 @@ export class StructType extends CustomType { } } -// TODO: implement setField(), convenience method. -// TODO: Hold fields in a map (by name), and use the order within "field definitions" to perform codec operations. export class Struct extends TypedValue { - private readonly fields: Field[] = []; + private readonly fields: Field[]; + private readonly fieldsByName: Map; /** - * Currently, one can only set fields at initialization time. Construction will be improved at a later time. + * One can only set fields at initialization time. */ constructor(type: StructType, fields: Field[]) { super(type); this.fields = fields; + this.fieldsByName = new Map(fields.map(field => [field.name, field])); this.checkTyping(); } @@ -44,6 +45,15 @@ export class Struct extends TypedValue { return this.fields; } + getFieldValue(name: string): any { + let field = this.fieldsByName.get(name); + if (field) { + return field.value.valueOf(); + } + + throw new ErrMissingFieldOnStruct(name, this.getType().getName()); + } + valueOf(): any { let result: any = {}; From c561037390d6ddbf5d285eadbf3956a753b8bbe9 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 11:39:44 +0200 Subject: [PATCH 24/37] Better factories for some typed values. --- src/smartcontracts/codec/option.ts | 2 +- .../interaction.local.net.spec.ts | 2 +- src/smartcontracts/interaction.spec.ts | 2 +- .../smartContract.local.net.spec.ts | 2 +- src/smartcontracts/typesystem/algebraic.ts | 20 +++++++++++++- src/smartcontracts/typesystem/factory.spec.ts | 27 +++++++++++++++++++ src/smartcontracts/typesystem/factory.ts | 16 +++++++++++ src/smartcontracts/typesystem/generic.ts | 2 +- src/smartcontracts/typesystem/types.ts | 2 +- 9 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 src/smartcontracts/typesystem/factory.spec.ts create mode 100644 src/smartcontracts/typesystem/factory.ts diff --git a/src/smartcontracts/codec/option.ts b/src/smartcontracts/codec/option.ts index 8bbe758e..1b556d05 100644 --- a/src/smartcontracts/codec/option.ts +++ b/src/smartcontracts/codec/option.ts @@ -15,7 +15,7 @@ export class OptionValueBinaryCodec { decodeNested(buffer: Buffer, type: Type): [OptionValue, number] { if (buffer[0] == 0x00) { - return [OptionValue.newMissingType(type), 1]; + return [OptionValue.newMissingTyped(type), 1]; } if (buffer[0] != 0x01) { diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index f246d714..03f02775 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -118,7 +118,7 @@ describe("test smart contract interactor", function () { OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), - new OptionalValue(new OptionalType(new BigUIntType())) + OptionValue.newMissing() ]).withGasLimit(new GasLimit(30000000)); let lotteryStatusInteraction = contract.methods.status([ diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 5c58d588..f6e0df8e 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -233,7 +233,7 @@ describe("test smart contract interactor", function() { OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), - new OptionalValue(new OptionalType(new BigUIntType())) + OptionalValue.newMissing() ]) .withGasLimit(new GasLimit(5000000)) ); diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index e220124f..959767cd 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -262,7 +262,7 @@ describe("test on local testnet", function () { OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), - new OptionalValue(new OptionalType(new BigUIntType())) + OptionalValue.newMissing() ] }); diff --git a/src/smartcontracts/typesystem/algebraic.ts b/src/smartcontracts/typesystem/algebraic.ts index 14bc3d03..7a7781fb 100644 --- a/src/smartcontracts/typesystem/algebraic.ts +++ b/src/smartcontracts/typesystem/algebraic.ts @@ -1,5 +1,5 @@ import { guardValueIsSet } from "../../utils"; -import { Type, TypeCardinality, TypedValue } from "./types"; +import { NullType, Type, TypeCardinality, TypedValue } from "./types"; /** * An optional is an algebraic type. It holds zero or one values. @@ -8,6 +8,16 @@ export class OptionalType extends Type { constructor(typeParameter: Type) { super("Optional", [typeParameter], TypeCardinality.variable(1)); } + + isAssignableFrom(type: Type): boolean { + if (!(type.hasJavascriptConstructor(OptionalType.name))) { + return false; + } + + let invariantTypeParameters = this.getFirstTypeParameter().equals(type.getFirstTypeParameter()); + let fakeCovarianceToNull = type.getFirstTypeParameter().hasJavascriptConstructor(NullType.name); + return invariantTypeParameters || fakeCovarianceToNull; + } } export class OptionalValue extends TypedValue { @@ -21,6 +31,14 @@ export class OptionalValue extends TypedValue { this.value = value; } + /** + * Creates an OptionalValue, as not provided (missing). + */ + static newMissing(): OptionalValue { + let type = new OptionalType(new NullType()); + return new OptionalValue(type); + } + isSet(): boolean { return this.value ? true : false; } diff --git a/src/smartcontracts/typesystem/factory.spec.ts b/src/smartcontracts/typesystem/factory.spec.ts new file mode 100644 index 00000000..88cc11f5 --- /dev/null +++ b/src/smartcontracts/typesystem/factory.spec.ts @@ -0,0 +1,27 @@ +import { assert } from "chai"; +import { TokenIdentifierType } from "./tokenIdentifier"; +import { Address } from "../../address"; +import { createListOfAddresses, createListOfTokensIdentifiers } from "./factory"; +import { ListType } from "./generic"; +import { AddressType } from "./address"; + +describe("test factory", () => { + it("should create lists of addresses", () => { + let addresses = [ + new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"), + new Address("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede"), + new Address("erd1fggp5ru0jhcjrp5rjqyqrnvhr3sz3v2e0fm3ktknvlg7mcyan54qzccnan") + ]; + + let list = createListOfAddresses(addresses); + assert.deepEqual(list.getType(), new ListType(new AddressType())); + assert.deepEqual(list.valueOf(), addresses); + }); + + it("should create lists of token identifiers", () => { + let identifiers = ["RIDE-7d18e9", "MEX-455c57"]; + let list = createListOfTokensIdentifiers(identifiers); + assert.deepEqual(list.getType(), new ListType(new TokenIdentifierType())); + assert.deepEqual(list.valueOf(), identifiers); + }); +}); diff --git a/src/smartcontracts/typesystem/factory.ts b/src/smartcontracts/typesystem/factory.ts new file mode 100644 index 00000000..bad8ade3 --- /dev/null +++ b/src/smartcontracts/typesystem/factory.ts @@ -0,0 +1,16 @@ +import { Address } from "../../address"; +import { AddressValue } from "./address"; +import { List } from "./generic"; +import { TokenIdentifierValue } from "./tokenIdentifier"; + +export function createListOfAddresses(addresses: Address[]): List { + let addressesTyped = addresses.map(address => new AddressValue(address)); + let list = List.fromItems(addressesTyped); + return list; +} + +export function createListOfTokensIdentifiers(identifiers: string[]): List { + let identifiersTyped = identifiers.map(identifier => new TokenIdentifierValue(identifier)); + let list = List.fromItems(identifiersTyped); + return list; +} diff --git a/src/smartcontracts/typesystem/generic.ts b/src/smartcontracts/typesystem/generic.ts index 26457156..fa974fff 100644 --- a/src/smartcontracts/typesystem/generic.ts +++ b/src/smartcontracts/typesystem/generic.ts @@ -46,7 +46,7 @@ export class OptionValue extends TypedValue { return new OptionValue(type); } - static newMissingType(type: Type): OptionValue { + static newMissingTyped(type: Type): OptionValue { return new OptionValue(new OptionType(type)); } diff --git a/src/smartcontracts/typesystem/types.ts b/src/smartcontracts/typesystem/types.ts index b8ae5672..b686fa42 100644 --- a/src/smartcontracts/typesystem/types.ts +++ b/src/smartcontracts/typesystem/types.ts @@ -93,7 +93,7 @@ export class Type { * * One exception though: for {@link OptionType}, we simulate covariance for missing (not provided) values. * For example, Option is assignable from Option. - * For more details, see the implementation of {@link OptionType}. + * For more details, see the implementation of {@link OptionType} and @{@link OptionalType}. * * Also see: * - https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) From 947d53d4e9aba4bd1b09d620ee22e72b7b578832 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 12:01:36 +0200 Subject: [PATCH 25/37] Fix test, bump alpha version: 10.0.0-alpha.3. --- package-lock.json | 2 +- package.json | 2 +- src/smartcontracts/interaction.local.net.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bee73212..0247f770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.2", + "version": "10.0.0-alpha.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b6529cea..3d992361 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.2", + "version": "10.0.0-alpha.3", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 03f02775..8d050afc 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -118,7 +118,7 @@ describe("test smart contract interactor", function () { OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), - OptionValue.newMissing() + OptionalValue.newMissing() ]).withGasLimit(new GasLimit(30000000)); let lotteryStatusInteraction = contract.methods.status([ From aeadee66f02f101e00fc187a9271241d509f7db5 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 12:15:31 +0200 Subject: [PATCH 26/37] Fix export, bump alpha version: 10.0.0-alpha.4. --- package-lock.json | 2 +- package.json | 2 +- src/smartcontracts/typesystem/index.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0247f770..6585053d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.3", + "version": "10.0.0-alpha.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3d992361..04c569e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.3", + "version": "10.0.0-alpha.4", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/smartcontracts/typesystem/index.ts b/src/smartcontracts/typesystem/index.ts index 201a037d..c4644499 100644 --- a/src/smartcontracts/typesystem/index.ts +++ b/src/smartcontracts/typesystem/index.ts @@ -12,6 +12,7 @@ export * from "./composite"; export * from "./contractInterface"; export * from "./endpoint"; export * from "./enum"; +export * from "./factory"; export * from "./fields"; export * from "./generic"; export * from "./genericArray"; From d4c9e2b9d0e2fddf2fd3183c70482977044d5167 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 13:34:56 +0200 Subject: [PATCH 27/37] Add "methodsAuto" on SmartContract, in addition to "methods". Performs automatic type inference (wrt. ABI endpoint definition), via the "native serializer". --- src/smartcontracts/nativeSerializer.spec.ts | 41 +++++++++++++++++++++ src/smartcontracts/nativeSerializer.ts | 15 +++----- src/smartcontracts/smartContract.ts | 20 +++++++++- src/testutils/wallets.ts | 4 ++ 4 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/smartcontracts/nativeSerializer.spec.ts diff --git a/src/smartcontracts/nativeSerializer.spec.ts b/src/smartcontracts/nativeSerializer.spec.ts new file mode 100644 index 00000000..ee26f422 --- /dev/null +++ b/src/smartcontracts/nativeSerializer.spec.ts @@ -0,0 +1,41 @@ +import { assert } from "chai"; +import { Address } from "../address"; +import { AddressType, BigUIntType, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, ListType, NullType, OptionalType, OptionType, U32Type } from "./typesystem"; +import { BytesType } from "./typesystem/bytes"; +import { NativeSerializer } from "./nativeSerializer"; + +describe("test native serializer", () => { + it("should perform type inference", async () => { + let endpointModifiers = new EndpointModifiers("", []); + let inputParameters = [ + new EndpointParameterDefinition("a", "a", new BigUIntType()), + new EndpointParameterDefinition("b", "b", new ListType(new AddressType())), + new EndpointParameterDefinition("c", "c", new BytesType()), + new EndpointParameterDefinition("d", "d", new OptionType(new U32Type())), + new EndpointParameterDefinition("e", "e", new OptionType(new U32Type())), + new EndpointParameterDefinition("f", "f", new OptionalType(new BytesType())) + ]; + let endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers); + + let a = 42; + let b = [new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"), new Address("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede")]; + let c = Buffer.from("abba", "hex"); + let d = null; + let e = 7; + // Let's not provide "f" + let typedValues = NativeSerializer.nativeToTypedValues([a, b, c, d, e], endpoint); + + assert.deepEqual(typedValues[0].getType(), new BigUIntType()); + assert.deepEqual(typedValues[0].valueOf().toNumber(), a); + assert.deepEqual(typedValues[1].getType(), new ListType(new AddressType())); + assert.deepEqual(typedValues[1].valueOf(), b); + assert.deepEqual(typedValues[2].getType(), new BytesType()); + assert.deepEqual(typedValues[2].valueOf(), c); + assert.deepEqual(typedValues[3].getType(), new OptionType(new NullType())); + assert.deepEqual(typedValues[3].valueOf(), null); + assert.deepEqual(typedValues[4].getType(), new OptionType(new U32Type())); + assert.deepEqual(typedValues[4].valueOf().toNumber(), e); + assert.deepEqual(typedValues[5].getType(), new OptionalType(new BytesType())); + assert.deepEqual(typedValues[5].valueOf(), null); + }); +}); diff --git a/src/smartcontracts/nativeSerializer.ts b/src/smartcontracts/nativeSerializer.ts index 25df6dd3..bc131614 100644 --- a/src/smartcontracts/nativeSerializer.ts +++ b/src/smartcontracts/nativeSerializer.ts @@ -1,10 +1,7 @@ import BigNumber from "bignumber.js"; import { AddressType, AddressValue, BigIntType, BigIntValue, BigUIntType, BigUIntValue, BooleanType, BooleanValue, BytesType, BytesValue, CompositeType, CompositeValue, EndpointDefinition, EndpointParameterDefinition, I16Type, I16Value, I32Type, I32Value, I64Type, I64Value, I8Type, I8Value, List, ListType, NumericalType, OptionalType, OptionalValue, OptionType, OptionValue, PrimitiveType, TokenIdentifierType, TokenIdentifierValue, TupleType, Type, TypedValue, U16Type, U16Value, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, VariadicType, VariadicValue } from "./typesystem"; -import { TestWallet } from "../testutils"; import { ArgumentErrorContext } from "./argumentErrorContext"; -import { SmartContract } from "./smartContract"; import { Struct, Field, StructType, Tuple } from "./typesystem"; -import { ContractWrapper } from "./wrapper/contractWrapper"; import { BalanceBuilder } from "../balanceBuilder"; import { Address } from "../address"; import { Code } from "./code"; @@ -13,7 +10,7 @@ import { ErrInvalidArgument } from "../errors"; export namespace NativeTypes { export type NativeBuffer = Buffer | string | BalanceBuilder; export type NativeBytes = Code | Buffer | string | BalanceBuilder; - export type NativeAddress = Address | string | Buffer | ContractWrapper | SmartContract | TestWallet; + export type NativeAddress = Address | string | Buffer | { getAddress(): Address }; } export namespace NativeSerializer { @@ -234,17 +231,15 @@ export namespace NativeSerializer { } export function convertNativeToAddress(native: NativeTypes.NativeAddress, errorContext: ArgumentErrorContext): Address { + if ((native).getAddress) { + return (native).getAddress(); + } + switch (native.constructor) { case Address: case Buffer: case String: return new Address(
native); - case ContractWrapper: - return (native).getAddress(); - case SmartContract: - return (native).getAddress(); - case TestWallet: - return (native).address; default: errorContext.convertError(native, "Address"); } diff --git a/src/smartcontracts/smartContract.ts b/src/smartcontracts/smartContract.ts index 43fe345e..78238517 100644 --- a/src/smartcontracts/smartContract.ts +++ b/src/smartcontracts/smartContract.ts @@ -17,6 +17,7 @@ import { TypedValue } from "./typesystem"; import { bigIntToBuffer } from "./codec/utils"; import BigNumber from "bignumber.js"; import { Interaction } from "./interaction"; +import { NativeSerializer } from "./nativeSerializer"; const createKeccakHash = require("keccak"); /** @@ -37,6 +38,15 @@ export class SmartContract implements ISmartContract { */ public readonly methods: { [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 methods}. + * Unlike {@link methods}, automatic type inference (wrt. ABI) is applied when using {@link methodsAuto}. + */ + public readonly methodsAuto: { [key: string]: (args?: any[]) => Interaction } = {}; + /** * Create a SmartContract object by providing its address on the Network. */ @@ -57,7 +67,7 @@ export class SmartContract implements ISmartContract { for (const definition of abi.getAllEndpoints()) { let functionName = definition.name; - // For each endpoint defined by the ABI, we attach a function to the "methods" object, + // 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.methods[functionName] = function (args?: TypedValue[]) { @@ -65,6 +75,14 @@ export class SmartContract implements ISmartContract { let interaction = new Interaction(contract, func, args || []); return interaction; }; + + this.methodsAuto[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; + }; } } diff --git a/src/testutils/wallets.ts b/src/testutils/wallets.ts index 2992600e..8dfefc90 100644 --- a/src/testutils/wallets.ts +++ b/src/testutils/wallets.ts @@ -82,6 +82,10 @@ export class TestWallet { this.account = new Account(this.address); } + getAddress(): Address { + return this.address; + } + async sync(provider: IProvider) { await this.account.sync(provider); return this; From 4ffa59650b6dcbcf73618e0ee709aac1ba6dbf74 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 21:40:22 +0200 Subject: [PATCH 28/37] Fix after self-review. --- src/errors.ts | 9 ++++ src/smartcontracts/resultsParser.spec.ts | 60 ++++++++++++++++++++++++ src/smartcontracts/resultsParser.ts | 60 ++++++++++++++---------- src/transactionLogs.ts | 4 +- 4 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 src/smartcontracts/resultsParser.spec.ts diff --git a/src/errors.ts b/src/errors.ts index f974f084..73d4563a 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -427,6 +427,15 @@ export class ErrTypingSystem extends Err { } } +/** + * Signals an error when parsing the contract results. + */ + export class ErrCannotParseContractResults extends Err { + public constructor(details: string) { + super(`cannot parse contract results: ${details}`); + } +} + /** * Signals a generic codec (encode / decode) error. */ diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts new file mode 100644 index 00000000..1332f8ce --- /dev/null +++ b/src/smartcontracts/resultsParser.spec.ts @@ -0,0 +1,60 @@ +import { assert } from "chai"; +import { BigUIntType, BigUIntValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition } from "./typesystem"; +import { BytesType, BytesValue } from "./typesystem/bytes"; +import { QueryResponse } from "./queryResponse"; +import { ReturnCode } from "./returnCode"; +import { ResultsParser } from "./resultsParser"; +import { TransactionOnNetwork } from "../transactionOnNetwork"; +import { SmartContractResultItem, SmartContractResults } from "./smartContractResults"; +import { Nonce } from "../nonce"; + +describe("test smart contract results parser", () => { + let parser = new ResultsParser(); + + it("should parse query response", async () => { + let endpointModifiers = new EndpointModifiers("", []); + let outputParameters = [ + new EndpointParameterDefinition("a", "a", new BigUIntType()), + new EndpointParameterDefinition("b", "b", new BytesType()) + ]; + let endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers); + + let queryResponse = new QueryResponse({ + returnData: [ + Buffer.from([42]).toString("base64"), + Buffer.from("abba", "hex").toString("base64") + ], + returnCode: ReturnCode.Ok, + returnMessage: "foobar" + }); + + let bundle = parser.parseQueryResponse(queryResponse, endpoint); + assert.deepEqual(bundle.returnCode, ReturnCode.Ok); + assert.equal(bundle.returnMessage, "foobar"); + assert.deepEqual(bundle.firstValue, new BigUIntValue(42)); + assert.deepEqual(bundle.secondValue, BytesValue.fromHex("abba")); + assert.lengthOf(bundle.values, 2); + }); + + it("should parse contract outcome", async () => { + let endpointModifiers = new EndpointModifiers("", []); + let outputParameters = [ + new EndpointParameterDefinition("a", "a", new BigUIntType()), + new EndpointParameterDefinition("b", "b", new BytesType()) + ]; + let endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers); + + let transactionOnNetwork = new TransactionOnNetwork({ + results: new SmartContractResults([ + new SmartContractResultItem({ nonce: new Nonce(7), data: "@6f6b@2a@abba" }) + ]) + }); + + let bundle = parser.parseOutcome(transactionOnNetwork, endpoint); + assert.deepEqual(bundle.returnCode, ReturnCode.Ok); + assert.equal(bundle.returnMessage, "ok"); + assert.deepEqual(bundle.firstValue, new BigUIntValue(42)); + assert.deepEqual(bundle.secondValue, BytesValue.fromHex("abba")); + assert.lengthOf(bundle.values, 2); + }); +}); diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index 6ccfd53c..b579933e 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -1,4 +1,4 @@ -import { ErrInvariantFailed } from "../errors"; +import { ErrCannotParseContractResults, ErrInvariantFailed } from "../errors"; import { TransactionOnNetwork } from "../transactionOnNetwork"; import { ArgSerializer } from "./argSerializer"; import { TypedOutcomeBundle, IResultsParser, UntypedOutcomeBundle } from "./interface"; @@ -6,6 +6,12 @@ import { QueryResponse } from "./queryResponse"; import { ReturnCode } from "./returnCode"; import { EndpointDefinition } from "./typesystem"; +enum WellKnownEvents { + OnTransactionCompleted = "completedTxEvent", + OnContractDeployment = "SCDeploy", + OnUserError = "signalError" +} + export class ResultsParser implements IResultsParser { parseQueryResponse(queryResponse: QueryResponse, endpoint: EndpointDefinition): TypedOutcomeBundle { let parts = queryResponse.getReturnDataParts(); @@ -29,12 +35,6 @@ export class ResultsParser implements IResultsParser { }; } - /** - * TODO: Improve this function. Currently, the implementation makes some (possibly incorrect) assumptions on the SCR & logs construction logic. - * - * @param transaction The transaction holding the contract execution outcome (results and logs). - * @param endpoint The endpoint definition (ABI) - */ parseOutcome(transaction: TransactionOnNetwork, endpoint: EndpointDefinition): TypedOutcomeBundle { let untypedBundle = this.parseUntypedOutcome(transaction); let values = new ArgSerializer().buffersToValues(untypedBundle.values, endpoint.output); @@ -48,26 +48,34 @@ export class ResultsParser implements IResultsParser { thirdValue: values[2] }; } - - // TODO: Handle code duplication. + + /** + * TODO: Upon further analysis, improve this function. Currently, the implementation makes some (possibly inaccurate) assumptions on the SCR & logs emission logic. + */ parseUntypedOutcome(transaction: TransactionOnNetwork): UntypedOutcomeBundle { let resultItems = transaction.results.getAll(); - // TODO: Fix! The filtering condition below IS NOT necessarily CORRECT (not sufficient). + // Let's search the result holding the returnData + // (possibly inaccurate logic at this moment) let resultItemWithReturnData = resultItems.find(item => item.nonce.valueOf() != 0); + // If we didn't find it, then fallback to events & logs: + // (possibly inaccurate logic at this moment) if (!resultItemWithReturnData) { let returnCode = ReturnCode.Unknown; - if (transaction.logs.findEventByIdentifier("completedTxEvent")) { + if (transaction.logs.findEventByIdentifier(WellKnownEvents.OnTransactionCompleted)) { + // We do not extract any return data. returnCode = ReturnCode.Ok; - } else if (transaction.logs.findEventByIdentifier("SCDeploy")) { - // TODO: Check if this logic is correct. + } else if (transaction.logs.findEventByIdentifier(WellKnownEvents.OnContractDeployment)) { + // When encountering this event, we assume a successful deployment. + // We do not extract any return data. + // (possibly inaccurate logic at this moment, especially in case of deployments from other contracts) returnCode = ReturnCode.Ok; - } else if (transaction.logs.findEventByIdentifier("signalError")) { + } else if (transaction.logs.findEventByIdentifier(WellKnownEvents.OnUserError)) { returnCode = ReturnCode.UserError; } - // TODO: Also handle "too much gas provided" (writeLog event) - in this case, the returnData (from the contract) is held in the event.data field. + // TODO: Also handle "too much gas provided" (writeLog event) - in this case, the returnData is held in the event.data field. return { returnCode: returnCode, @@ -77,24 +85,28 @@ export class ResultsParser implements IResultsParser { } let parts = resultItemWithReturnData.getDataParts(); + let { returnCode, returnDataParts } = this.sliceDataParts(parts); + + return { + returnCode: returnCode, + returnMessage: returnCode.toString(), + values: returnDataParts + }; + } + + private sliceDataParts(parts: Buffer[]): { returnCode: ReturnCode, returnDataParts: Buffer[] } { let emptyReturnPart = parts[0] || Buffer.from([]); let returnCodePart = parts[1] || Buffer.from([]); let returnDataParts = parts.slice(2); if (emptyReturnPart.length != 0) { - throw new ErrInvariantFailed("Cannot parse contract return data. No leading empty part."); + throw new ErrCannotParseContractResults("no leading empty part"); } - if (returnCodePart.length == 0) { - throw new ErrInvariantFailed("Cannot parse contract return code."); + throw new ErrCannotParseContractResults("no return code"); } let returnCode = ReturnCode.fromBuffer(returnCodePart); - - return { - returnCode: returnCode, - returnMessage: returnCode.toString(), - values: returnDataParts - }; + return { returnCode, returnDataParts }; } } diff --git a/src/transactionLogs.ts b/src/transactionLogs.ts index aad92869..f1ef9cbf 100644 --- a/src/transactionLogs.ts +++ b/src/transactionLogs.ts @@ -20,7 +20,7 @@ export class TransactionLogs { return new TransactionLogs(address, events); } - requireEventByIdentifier(identifier: string) { + requireEventByIdentifier(identifier: string): TransactionEvent { let event = this.findEventByIdentifier(identifier); if (event) { return event; @@ -29,7 +29,7 @@ export class TransactionLogs { throw new ErrTransactionEventNotFound(identifier); } - findEventByIdentifier(identifier: string) { + findEventByIdentifier(identifier: string): TransactionEvent | undefined { let event = this.events.filter(event => event.identifier == identifier)[0]; return event; } From e672d9e94181590040b29ea4b0721fa4234f1067 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 21:46:42 +0200 Subject: [PATCH 29/37] Bit of cleanup etc. --- .../wrapper/deprecatedContractResults.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/smartcontracts/wrapper/deprecatedContractResults.ts b/src/smartcontracts/wrapper/deprecatedContractResults.ts index 573e62c5..5faa6225 100644 --- a/src/smartcontracts/wrapper/deprecatedContractResults.ts +++ b/src/smartcontracts/wrapper/deprecatedContractResults.ts @@ -20,7 +20,6 @@ export function interpretExecutionResults(endpoint: EndpointDefinition, transact let returnCode = immediateResult.getReturnCode(); return { - transactionOnNetwork: transactionOnNetwork, smartContractResults: smartContractResults, immediateResult, resultingCalls, @@ -35,9 +34,11 @@ export function interpretExecutionResults(endpoint: EndpointDefinition, transact * The SCRs are more alike a graph. */ export interface ExecutionResultsBundle { - transactionOnNetwork: TransactionOnNetwork; smartContractResults: SmartContractResults; immediateResult: TypedResult; + /** + * @deprecated Most probably, we should use logs & events instead + */ resultingCalls: TypedResult[]; values: TypedValue[]; firstValue: TypedValue; @@ -53,7 +54,7 @@ export interface QueryResponseBundle { /** * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. - * The SCRs are more alike a graph. + * The SCRs are more like a graph. */ export function findImmediateResult(results: SmartContractResults): TypedResult | undefined { let immediateItem = results.getAll().filter(item => isImmediateResult(item))[0]; @@ -65,7 +66,7 @@ export function findImmediateResult(results: SmartContractResults): TypedResult /** * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. - * The SCRs are more alike a graph. + * The SCRs are more like a graph. */ export function findResultingCalls(results: SmartContractResults): TypedResult[] { let otherItems = results.getAll().filter(item => !isImmediateResult(item)); @@ -75,7 +76,7 @@ export function findResultingCalls(results: SmartContractResults): TypedResult[] /** * @deprecated The concept of immediate results / resulting calls does not exist in the Protocol / in the API. - * The SCRs are more alike a graph. + * The SCRs are more like a graph. */ function isImmediateResult(item: SmartContractResultItem): boolean { return item.nonce.valueOf() != 0; From e7dcd90c57178379bf9bbd38490ea7ff7b03a0cb Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 21:57:35 +0200 Subject: [PATCH 30/37] Undo workflow changes etc. --- .../workflows/erdjs-publish-alpha-beta.yml | 29 +++++++++++++++++++ .github/workflows/erdjs-publish.yml | 13 ++++++++- .../wrapper/deprecatedContractResults.ts | 9 ++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/erdjs-publish-alpha-beta.yml diff --git a/.github/workflows/erdjs-publish-alpha-beta.yml b/.github/workflows/erdjs-publish-alpha-beta.yml new file mode 100644 index 00000000..49e22765 --- /dev/null +++ b/.github/workflows/erdjs-publish-alpha-beta.yml @@ -0,0 +1,29 @@ +name: Publish erdjs (alpha / beta) + +on: + workflow_dispatch: + inputs: + channel: + type: choice + description: NPM channel + options: + - alpha + - beta + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + + - run: npm ci + - run: npm test + + - name: Publish to npmjs + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + run: npm publish --access=public --tag=${{ github.event.inputs.channel }} diff --git a/.github/workflows/erdjs-publish.yml b/.github/workflows/erdjs-publish.yml index 77aa6306..4223a85a 100644 --- a/.github/workflows/erdjs-publish.yml +++ b/.github/workflows/erdjs-publish.yml @@ -13,10 +13,21 @@ jobs: node-version: 12 registry-url: https://registry.npmjs.org/ + - name: Create release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo " - [npmjs](https://www.npmjs.com/package/@elrondnetwork/erdjs)" >> notes.txt + echo " - [CHANGELOG](https://github.com/ElrondNetwork/elrond-sdk-erdjs/blob/main/CHANGELOG.md)" >> notes.txt + echo "" >> notes.txt + + RELEASE_TAG=v$(node -p "require('./package.json').version") + gh release create $RELEASE_TAG --title="$RELEASE_TAG" --generate-notes --notes-file=notes.txt + - run: npm ci - run: npm test - name: Publish to npmjs env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - run: npm publish --access=public --tag=alpha + run: npm publish --access=public diff --git a/src/smartcontracts/wrapper/deprecatedContractResults.ts b/src/smartcontracts/wrapper/deprecatedContractResults.ts index 5faa6225..8b652b7b 100644 --- a/src/smartcontracts/wrapper/deprecatedContractResults.ts +++ b/src/smartcontracts/wrapper/deprecatedContractResults.ts @@ -1,3 +1,12 @@ +// deprecatedContractResults.ts +/** + * This file contains the old (before erdjs 10) logic dealing with SCRs. + * The components / functions were moved here in order to change erdjs-repl / contract wrapper components as little as possible. + * When splitting the erdjs repository into multiple ones (for wallet providers, walletcore etc.), we should consider extracting erdjs-repl / contract wrappers + * to a separate repository, as well. Though excellent for CLI, these components are not suited for minimal web dApps - different use cases. + * @module + */ + import { TransactionOnNetwork } from "../../transactionOnNetwork"; import { ArgSerializer } from "../argSerializer"; import { QueryResponse } from "../queryResponse"; From 3c73853acc85e6f5dd6dba7db4da7eaab1e53e15 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 22:15:24 +0200 Subject: [PATCH 31/37] Update changelog, bump alpha version. --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40a4fcad..cc2cef3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## Unreleased - TBD +## [10.0.0] + - [Fix SCRs interpretation, improve SC interactions](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/159) + + **Breaking changes** + +- `ExecutionResultsBundle` and `QueryResponseBundle` have been removed, and replaced by `TypedOutcomeBundle` (and its untyped counterpart, `UntypedOutcomeBundle`). + - `SmartContractResults` has been changed to not use the concepts `immediate result` and `resulting calls` anymore. Instead, interpreting `SmartContractResults.items` is now the responsibility of the `ResultsParser` (on which the contract controllers depend). + - `TokenIdentifierValue` is constructed using a `string`, not a `buffer`. It's `valueOf()` it now a string, as well. + - `DefaultInteractionRunner` has been removed, and replaced by **smart contract controllers**. + - `StrictChecker` has been renamed to `InteractionChecker`. It's public interface - the function `checkInteraction()` - has changed as well (it also requires the endpoint definition now, as a second parameter). + - The functions `getReceipt()`, `getSmartContractResults()` and `getLogs()` of `TransactionOnNetwork` have been removed. The underlying properties are now public. + ## [9.2.2] - [Wallet Provider: add "options" on tx sign return value](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/157) diff --git a/package-lock.json b/package-lock.json index 6585053d..007deca4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.4", + "version": "10.0.0-alpha.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 04c569e6..3ee738e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elrondnetwork/erdjs", - "version": "10.0.0-alpha.4", + "version": "10.0.0-alpha.5", "description": "Smart Contracts interaction framework", "main": "out/index.js", "types": "out/index.d.js", From d73edd05d778ae9c2211cc2e21475e76839b7936 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 22:17:38 +0200 Subject: [PATCH 32/37] Update changelog. --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2cef3b..2a6fd220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,14 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - TBD ## [10.0.0] - - [Fix SCRs interpretation, improve SC interactions](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/159) + - [Breaking changes: improve contract interactions and interpretation of contract results](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/159) **Breaking changes** -- `ExecutionResultsBundle` and `QueryResponseBundle` have been removed, and replaced by `TypedOutcomeBundle` (and its untyped counterpart, `UntypedOutcomeBundle`). + - `ExecutionResultsBundle` and `QueryResponseBundle` have been removed, and replaced by `TypedOutcomeBundle` (and its untyped counterpart, `UntypedOutcomeBundle`). - `SmartContractResults` has been changed to not use the concepts `immediate result` and `resulting calls` anymore. Instead, interpreting `SmartContractResults.items` is now the responsibility of the `ResultsParser` (on which the contract controllers depend). - `TokenIdentifierValue` is constructed using a `string`, not a `buffer`. It's `valueOf()` it now a string, as well. + - The `Interaction` constructor does not receive the `interpretingFunction` parameter anymore. - `DefaultInteractionRunner` has been removed, and replaced by **smart contract controllers**. - `StrictChecker` has been renamed to `InteractionChecker`. It's public interface - the function `checkInteraction()` - has changed as well (it also requires the endpoint definition now, as a second parameter). - The functions `getReceipt()`, `getSmartContractResults()` and `getLogs()` of `TransactionOnNetwork` have been removed. The underlying properties are now public. From 8617eafa506cd35363cfc0e4c7f3b54302030fb6 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 22 Mar 2022 22:51:14 +0200 Subject: [PATCH 33/37] Update changelog. --- CHANGELOG.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a6fd220..c1f8a7a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,31 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [10.0.0] - [Breaking changes: improve contract interactions and interpretation of contract results](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/159) - **Breaking changes** +**Breaking changes** - `ExecutionResultsBundle` and `QueryResponseBundle` have been removed, and replaced by `TypedOutcomeBundle` (and its untyped counterpart, `UntypedOutcomeBundle`). - `SmartContractResults` has been changed to not use the concepts `immediate result` and `resulting calls` anymore. Instead, interpreting `SmartContractResults.items` is now the responsibility of the `ResultsParser` (on which the contract controllers depend). + - Redesigned `QueryResponse`, changed most of its public interface. Results interpretation is now the responsibility of the results parser, called by the smart contract controllers. + - `interpretExecutionResults()` and `interpretExecutionResults()` do not exist on the `Interaction` object anymore. Now, querying / executing an interaction against the controller will return the interpreted results. - `TokenIdentifierValue` is constructed using a `string`, not a `buffer`. It's `valueOf()` it now a string, as well. - The `Interaction` constructor does not receive the `interpretingFunction` parameter anymore. + - `Interaction.getInterpretingFunction()` and `Interaction.getExecutingFunction()` have been removed, replaced by `Interaction.getFunction()`. - `DefaultInteractionRunner` has been removed, and replaced by **smart contract controllers**. - `StrictChecker` has been renamed to `InteractionChecker`. It's public interface - the function `checkInteraction()` - has changed as well (it also requires the endpoint definition now, as a second parameter). - The functions `getReceipt()`, `getSmartContractResults()` and `getLogs()` of `TransactionOnNetwork` have been removed. The underlying properties are now public. + - Renamed `OptionValue.newMissingType()` to `OptionValue.newMissingTyped()` + +**Other changes** + + - `SmartContract`, in addition to `methods`, now also has a `methodAuto` object that allows one to create interactions without explicitly specifying the types of the arguments. Automatic type inference (within erdjs' typesystem) is leveraged. The type inference system was implemented in the past, in the `nativeSerializer` component - PR https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/9 by @claudiu725. + - Added utility function `getFieldValue()` on `Struct` and `EnumValue`. + - Refactoring in the `networkProvider` package (under development, in order to merge the provider interfaces under a single one) + - Added utility function `Interaction.useThenIncrementNonceOf()` + - Fixed `nativeSerializer` to not depend on `SmartContract`, `ContractWrapper` and `TestWallet` anymore (gathered under an interface). + - Replaced the old `lottery-egld` with the new `lottery-esdt` in integration tests. + - Added missing tests for some components: `nativeSerializer`, `struct`, `enum`. + - Added utility function `OptionalValue.newMissing()`. Added "fake" covariance wrt. "null type parameter" (when value is missing) on `OptionalType`. + - Added utility functions (typed value factories): `createListOfAddresses`, `createListOfTokensIdentifiers`. ## [9.2.2] - [Wallet Provider: add "options" on tx sign return value](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/157) From f77d8f524a9aca77cc0fe17499431f29fab70754 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 23 Mar 2022 08:18:31 +0200 Subject: [PATCH 34/37] Update changelog, fix export. --- CHANGELOG.md | 1 + src/smartcontracts/index.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f8a7a4..ca186c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - `StrictChecker` has been renamed to `InteractionChecker`. It's public interface - the function `checkInteraction()` - has changed as well (it also requires the endpoint definition now, as a second parameter). - The functions `getReceipt()`, `getSmartContractResults()` and `getLogs()` of `TransactionOnNetwork` have been removed. The underlying properties are now public. - Renamed `OptionValue.newMissingType()` to `OptionValue.newMissingTyped()` + - Queries with a return code different than `Ok` do not automatically throw an exception anymore (`assertSuccess()` has to be called explicitly in order to throw). **Other changes** diff --git a/src/smartcontracts/index.ts b/src/smartcontracts/index.ts index 138f5115..7f2995a3 100644 --- a/src/smartcontracts/index.ts +++ b/src/smartcontracts/index.ts @@ -11,10 +11,12 @@ export * from "./codeMetadata"; export * from "./smartContractController"; export * from "./function"; export * from "./interaction"; +export * from "./interactionChecker"; export * from "./interface"; export * from "./nativeSerializer"; export * from "./query"; export * from "./queryResponse"; +export * from "./resultsParser"; export * from "./returnCode"; export * from "./smartContract"; export * from "./smartContractResults"; From 062ada741ff03c69182c3c6bb8867eb33505956b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 23 Mar 2022 18:03:08 +0200 Subject: [PATCH 35/37] Update CHANGELOG.md Co-authored-by: Claudiu-Marcel Bruda --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca186c9a..644f56a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,8 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - `ExecutionResultsBundle` and `QueryResponseBundle` have been removed, and replaced by `TypedOutcomeBundle` (and its untyped counterpart, `UntypedOutcomeBundle`). - `SmartContractResults` has been changed to not use the concepts `immediate result` and `resulting calls` anymore. Instead, interpreting `SmartContractResults.items` is now the responsibility of the `ResultsParser` (on which the contract controllers depend). - Redesigned `QueryResponse`, changed most of its public interface. Results interpretation is now the responsibility of the results parser, called by the smart contract controllers. - - `interpretExecutionResults()` and `interpretExecutionResults()` do not exist on the `Interaction` object anymore. Now, querying / executing an interaction against the controller will return the interpreted results. - - `TokenIdentifierValue` is constructed using a `string`, not a `buffer`. It's `valueOf()` it now a string, as well. + - `interpretQueryResponse()` and `interpretExecutionResults()` do not exist on the `Interaction` object anymore. Now, querying / executing an interaction against the controller will return the interpreted results. + - `TokenIdentifierValue` is constructed using a `string`, not a `buffer`. Its `valueOf()` is now a string, as well. - The `Interaction` constructor does not receive the `interpretingFunction` parameter anymore. - `Interaction.getInterpretingFunction()` and `Interaction.getExecutingFunction()` have been removed, replaced by `Interaction.getFunction()`. - `DefaultInteractionRunner` has been removed, and replaced by **smart contract controllers**. From a03830ec6460511a0cc9f7156da2dff1d8566b7d Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Thu, 24 Mar 2022 14:05:39 +0200 Subject: [PATCH 36/37] Fix after review. --- CHANGELOG.md | 2 +- src/smartcontracts/index.ts | 1 - src/smartcontracts/typesystem/factory.spec.ts | 4 ++-- src/smartcontracts/typesystem/factory.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6688ed67..f8a4d7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - Replaced the old `lottery-egld` with the new `lottery-esdt` in integration tests. - Added missing tests for some components: `nativeSerializer`, `struct`, `enum`. - Added utility function `OptionalValue.newMissing()`. Added "fake" covariance wrt. "null type parameter" (when value is missing) on `OptionalType`. - - Added utility functions (typed value factories): `createListOfAddresses`, `createListOfTokensIdentifiers`. + - Added utility functions (typed value factories): `createListOfAddresses`, `createListOfTokenIdentifiers`. ## [9.2.3] - [Fix log level in transaction watcher.](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/160) diff --git a/src/smartcontracts/index.ts b/src/smartcontracts/index.ts index 7f2995a3..3e08174a 100644 --- a/src/smartcontracts/index.ts +++ b/src/smartcontracts/index.ts @@ -20,7 +20,6 @@ export * from "./resultsParser"; export * from "./returnCode"; export * from "./smartContract"; export * from "./smartContractResults"; -export * from "./interactionChecker"; export * from "./transactionPayloadBuilders"; export * from "./typesystem"; export * from "./wrapper"; diff --git a/src/smartcontracts/typesystem/factory.spec.ts b/src/smartcontracts/typesystem/factory.spec.ts index 88cc11f5..afe82de1 100644 --- a/src/smartcontracts/typesystem/factory.spec.ts +++ b/src/smartcontracts/typesystem/factory.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import { TokenIdentifierType } from "./tokenIdentifier"; import { Address } from "../../address"; -import { createListOfAddresses, createListOfTokensIdentifiers } from "./factory"; +import { createListOfAddresses, createListOfTokenIdentifiers } from "./factory"; import { ListType } from "./generic"; import { AddressType } from "./address"; @@ -20,7 +20,7 @@ describe("test factory", () => { it("should create lists of token identifiers", () => { let identifiers = ["RIDE-7d18e9", "MEX-455c57"]; - let list = createListOfTokensIdentifiers(identifiers); + let list = createListOfTokenIdentifiers(identifiers); assert.deepEqual(list.getType(), new ListType(new TokenIdentifierType())); assert.deepEqual(list.valueOf(), identifiers); }); diff --git a/src/smartcontracts/typesystem/factory.ts b/src/smartcontracts/typesystem/factory.ts index bad8ade3..b4870829 100644 --- a/src/smartcontracts/typesystem/factory.ts +++ b/src/smartcontracts/typesystem/factory.ts @@ -9,7 +9,7 @@ export function createListOfAddresses(addresses: Address[]): List { return list; } -export function createListOfTokensIdentifiers(identifiers: string[]): List { +export function createListOfTokenIdentifiers(identifiers: string[]): List { let identifiersTyped = identifiers.map(identifier => new TokenIdentifierValue(identifier)); let list = List.fromItems(identifiersTyped); return list; From cbf693d38fd63b11b9d0e3f4b384d664cc94b6b8 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Thu, 24 Mar 2022 14:10:14 +0200 Subject: [PATCH 37/37] Update tests, use controller.deploy(). --- .../interaction.local.net.spec.ts | 63 ++++++++++++------- src/smartcontracts/interaction.spec.ts | 2 +- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 8d050afc..8dec908b 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -1,6 +1,6 @@ import { DefaultSmartContractController } from "./smartContractController"; import { SmartContract } from "./smartContract"; -import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, TypedValue, U32Value } from "./typesystem"; +import { BigUIntValue, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadContractCode, loadTestWallets, TestWallet } from "../testutils"; import { SmartContractAbi } from "./abi"; import { assert } from "chai"; @@ -21,7 +21,7 @@ describe("test smart contract interactor", function () { }); it("should interact with 'answer' (local testnet)", async function () { - this.timeout(60000); + this.timeout(80000); let abiRegistry = await loadAbiRegistry(["src/testdata/answer.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["answer"]); @@ -32,7 +32,18 @@ describe("test smart contract interactor", function () { // because the Transaction objects created under the hood point to the "default" NetworkConfig. await NetworkConfig.getDefault().sync(provider); await alice.sync(provider); - await deploy(contract, "src/testdata/answer.wasm", new GasLimit(3000000), []); + + // Deploy the contract + let deployTransaction = contract.deploy({ + code: await loadContractCode("src/testdata/answer.wasm"), + gasLimit: new GasLimit(3000000), + initArguments: [] + }); + + deployTransaction.setNonce(alice.account.getNonceThenIncrement()); + await alice.signer.sign(deployTransaction); + let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + assert.isTrue(returnCode.isSuccess()); let interaction = contract.methods.getUltimateAnswer().withGasLimit(new GasLimit(3000000)); @@ -68,7 +79,18 @@ describe("test smart contract interactor", function () { // because the Transaction objects created under the hood point to the "default" NetworkConfig. await NetworkConfig.getDefault().sync(provider); await alice.sync(provider); - await deploy(contract, "src/testdata/counter.wasm", new GasLimit(3000000), []); + + // Deploy the contract + let deployTransaction = contract.deploy({ + code: await loadContractCode("src/testdata/counter.wasm"), + gasLimit: new GasLimit(3000000), + initArguments: [] + }); + + deployTransaction.setNonce(alice.account.getNonceThenIncrement()); + await alice.signer.sign(deployTransaction); + let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + assert.isTrue(returnCode.isSuccess()); let getInteraction = contract.methods.get(); let incrementInteraction = (contract.methods.increment()).withGasLimit(new GasLimit(3000000)); @@ -96,7 +118,7 @@ describe("test smart contract interactor", function () { }); it("should interact with 'lottery-esdt' (local testnet)", async function () { - this.timeout(120000); + this.timeout(140000); let abiRegistry = await loadAbiRegistry(["src/testdata/lottery-esdt.abi.json"]); let abi = new SmartContractAbi(abiRegistry, ["Lottery"]); @@ -107,7 +129,18 @@ describe("test smart contract interactor", function () { // because the Transaction objects created under the hood point to the "default" NetworkConfig. await NetworkConfig.getDefault().sync(provider); await alice.sync(provider); - await deploy(contract, "src/testdata/lottery-esdt.wasm", new GasLimit(100000000), []); + + // Deploy the contract + let deployTransaction = contract.deploy({ + code: await loadContractCode("src/testdata/lottery-esdt.wasm"), + gasLimit: new GasLimit(100000000), + initArguments: [] + }); + + deployTransaction.setNonce(alice.account.getNonceThenIncrement()); + await alice.signer.sign(deployTransaction); + let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + assert.isTrue(returnCode.isSuccess()); let startInteraction = contract.methods.start([ BytesValue.fromUTF8("lucky"), @@ -164,22 +197,4 @@ describe("test smart contract interactor", function () { prize_pool: new BigNumber("0") }); }); - - /** - * Deploy is not currently supported by interactors yet. - * We will deploy the contracts using the existing approach. - */ - async function deploy(contract: SmartContract, path: string, gasLimit: GasLimit, initArguments: TypedValue[]): Promise { - let transactionDeploy = contract.deploy({ - code: await loadContractCode(path), - gasLimit: gasLimit, - initArguments: initArguments - }); - - // In these tests, all contracts are deployed by Alice. - transactionDeploy.setNonce(alice.account.getNonceThenIncrement()); - await alice.signer.sign(transactionDeploy); - await transactionDeploy.send(provider); - await transactionDeploy.awaitExecuted(provider); - } }); diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index f6e0df8e..75e03be8 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -1,6 +1,6 @@ import { DefaultSmartContractController } from "./smartContractController"; import { SmartContract } from "./smartContract"; -import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; +import { BigUIntValue, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { loadAbiRegistry, loadTestWallets,