From 2487cc9157854835b3f1c890ca914065310c93ab Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 15 Apr 2024 16:18:44 +0300 Subject: [PATCH 01/16] add missing role on fungible token --- ...anagementTransactionIntentsFactory.spec.ts | 41 +++++++++++++++++++ .../tokenManagementTransactionsFactory.ts | 2 + 2 files changed, 43 insertions(+) diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts index aa5b7263..37b677f3 100644 --- a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -133,6 +133,47 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.value, config.issueCost); }); + it("should create 'Transaction' for setting spcial role on fungible token", () => { + const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken({ + sender: frank.address, + user: grace.address, + tokenIdentifier: "FRANK-11ce3e", + addRoleLocalMint: true, + addRoleLocalBurn: false, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@45534454526f6c654c6f63616c4d696e74", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for setting all spcial roles on fungible token", () => { + const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken({ + sender: frank.address, + user: grace.address, + tokenIdentifier: "FRANK-11ce3e", + addRoleLocalMint: true, + addRoleLocalBurn: true, + addRoleESDTTransferRole: true, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@45534454526f6c654c6f63616c4d696e74@45534454526f6c654c6f63616c4275726e@455344545472616e73666572526f6c65", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.value, 0n); + }); + it("should create 'Transaction' for setting spcial role on non-fungible token", () => { const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnNonFungibleToken({ sender: frank.address, diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts index 86662c00..de6afde5 100644 --- a/src/transactionsFactories/tokenManagementTransactionsFactory.ts +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -299,11 +299,13 @@ export class TokenManagementTransactionsFactory { tokenIdentifier: string; addRoleLocalMint: boolean; addRoleLocalBurn: boolean; + addRoleESDTTransferRole?: boolean; }): Transaction { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; options.addRoleLocalMint ? args.push(new StringValue("ESDTRoleLocalMint")) : 0; options.addRoleLocalBurn ? args.push(new StringValue("ESDTRoleLocalBurn")) : 0; + options.addRoleESDTTransferRole ? args.push(new StringValue("ESDTTransferRole")) : 0; const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; From bf6a29b1b69d48866406327de8b3c3609b90db63 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 16 Apr 2024 11:07:53 +0300 Subject: [PATCH 02/16] fix typos --- .../tokenManagementTransactionIntentsFactory.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts index 37b677f3..dd39f03e 100644 --- a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -133,7 +133,7 @@ describe("test token management transactions factory", () => { assert.deepEqual(transaction.value, config.issueCost); }); - it("should create 'Transaction' for setting spcial role on fungible token", () => { + it("should create 'Transaction' for setting special role on fungible token", () => { const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken({ sender: frank.address, user: grace.address, @@ -153,7 +153,7 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for setting all spcial roles on fungible token", () => { + it("should create 'Transaction' for setting all special roles on fungible token", () => { const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnFungibleToken({ sender: frank.address, user: grace.address, @@ -174,7 +174,7 @@ describe("test token management transactions factory", () => { assert.equal(transaction.value, 0n); }); - it("should create 'Transaction' for setting spcial role on non-fungible token", () => { + it("should create 'Transaction' for setting special role on non-fungible token", () => { const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnNonFungibleToken({ sender: frank.address, user: grace.address, From 6379548cbb9df7f51c83dc25c722efd0b71d3aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 16 Apr 2024 15:41:12 +0300 Subject: [PATCH 03/16] In "createTransactionForUpgrade()", use the "upgrade" definition, if available. --- .../smartContractTransactionsFactory.ts | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index 832d8ae3..a968a65a 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -2,6 +2,7 @@ import { Address } from "../address"; import { CONTRACT_DEPLOY_ADDRESS, VM_TYPE_WASM_VM } from "../constants"; import { Err, ErrBadUsage } from "../errors"; import { IAddress } from "../interface"; +import { Logger } from "../logger"; import { ArgSerializer, CodeMetadata, ContractFunction, EndpointDefinition } from "../smartcontracts"; import { NativeSerializer } from "../smartcontracts/nativeSerializer"; import { TokenComputer, TokenTransfer } from "../tokens"; @@ -56,15 +57,17 @@ export class SmartContractTransactionsFactory { const isPayableBySmartContract = options.isPayableBySmartContract ?? true; const args = options.arguments || []; const metadata = new CodeMetadata(isUpgradeable, isReadable, isPayable, isPayableBySmartContract); - let parts = [byteArrayToHex(options.bytecode), byteArrayToHex(VM_TYPE_WASM_VM), metadata.toString()]; - const preparedArgs = this.argsToDataParts(args, this.abi?.constructorDefinition); - parts = parts.concat(preparedArgs); + + const dataParts = [byteArrayToHex(options.bytecode), byteArrayToHex(VM_TYPE_WASM_VM), metadata.toString()]; + const endpoint = this.abi?.constructorDefinition; + const preparedArgs = this.argsToDataParts(args, endpoint); + dataParts.push(...preparedArgs); return new TransactionBuilder({ config: this.config, sender: options.sender, receiver: Address.fromBech32(CONTRACT_DEPLOY_ADDRESS), - dataParts: parts, + dataParts: dataParts, gasLimit: options.gasLimit, addDataMovementGas: false, amount: nativeTransferAmount, @@ -107,7 +110,10 @@ export class SmartContractTransactionsFactory { } dataParts.push(dataParts.length ? utf8ToHex(options.function) : options.function); - dataParts = dataParts.concat(this.argsToDataParts(args, this.abi?.getEndpoint(options.function))); + + const endpoint = this.abi?.getEndpoint(options.function); + const preparedArgs = this.argsToDataParts(args, endpoint); + dataParts.push(...preparedArgs); return new TransactionBuilder({ config: this.config, @@ -142,21 +148,40 @@ export class SmartContractTransactionsFactory { const args = options.arguments || []; const metadata = new CodeMetadata(isUpgradeable, isReadable, isPayable, isPayableBySmartContract); - let parts = ["upgradeContract", byteArrayToHex(options.bytecode), metadata.toString()]; - const preparedArgs = this.argsToDataParts(args, this.abi?.constructorDefinition); - parts = parts.concat(preparedArgs); + const dataParts = ["upgradeContract", byteArrayToHex(options.bytecode), metadata.toString()]; + const endpoint = this.getEndpointForUpgrade(); + const preparedArgs = this.argsToDataParts(args, endpoint); + dataParts.push(...preparedArgs); return new TransactionBuilder({ config: this.config, sender: options.sender, receiver: options.contract, - dataParts: parts, + dataParts: dataParts, gasLimit: options.gasLimit, addDataMovementGas: false, amount: nativeTransferAmount, }).build(); } + private getEndpointForUpgrade(): EndpointDefinition | undefined { + if (!this.abi) { + return undefined; + } + + try { + return this.abi.getEndpoint("upgrade"); + } catch (error) { + // Contracts written using an old Rust framework and deployed prior Sirius might not describe the 'upgrade' endpoint in the ABI. + + Logger.warn( + "In the ABI, cannot find the 'upgrade' endpoint definition. Will use the constructor definition (fallback).", + ); + + return this.abi.constructorDefinition; + } + } + private argsToDataParts(args: any[], endpoint?: EndpointDefinition): string[] { if (endpoint) { const typedArgs = NativeSerializer.nativeToTypedValues(args, endpoint); @@ -166,6 +191,7 @@ export class SmartContractTransactionsFactory { if (this.areArgsOfTypedValue(args)) { return new ArgSerializer().valuesToStrings(args); } + throw new Err("Can't convert args to TypedValues"); } @@ -175,6 +201,7 @@ export class SmartContractTransactionsFactory { return false; } } + return true; } } From b14785dc65ec7f8a4ed1fdf2afa1c4bc31ce72c4 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 16 Apr 2024 16:29:37 +0300 Subject: [PATCH 04/16] implement AccountTransactionsFactory --- .../accountTransactionsFactory.spec.ts | 78 ++++++++++++ .../accountTransactionsFactory.ts | 120 ++++++++++++++++++ .../delegationTransactionsFactory.ts | 6 +- src/transactionsFactories/index.ts | 1 + .../smartContractTransactionsFactory.spec.ts | 37 ++++++ .../smartContractTransactionsFactory.ts | 38 +++++- .../tokenManagementTransactionsFactory.ts | 8 +- .../transactionsFactoryConfig.ts | 22 +++- 8 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 src/transactionsFactories/accountTransactionsFactory.spec.ts create mode 100644 src/transactionsFactories/accountTransactionsFactory.ts diff --git a/src/transactionsFactories/accountTransactionsFactory.spec.ts b/src/transactionsFactories/accountTransactionsFactory.spec.ts new file mode 100644 index 00000000..5db44df3 --- /dev/null +++ b/src/transactionsFactories/accountTransactionsFactory.spec.ts @@ -0,0 +1,78 @@ +import { assert } from "chai"; +import { Address } from "../address"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; +import { AccountTransactionsFactory } from "./accountTransactionsFactory"; + +describe("test account transactions factory", function () { + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const factory = new AccountTransactionsFactory({ config: config }); + + it("should create 'Transaction' for saving key value", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const keyValuePairs = new Map([[Buffer.from("6b657930", "hex"), Buffer.from("76616c756530", "hex")]]); + + const transaction = factory.createTransactionForSavingKeyValue({ + sender: sender, + keyValuePairs: keyValuePairs, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.deepEqual(Buffer.from(transaction.data).toString(), "SaveKeyValue@6b657930@76616c756530"); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 271000n); + }); + + it("should create 'Transaction' for setting guardian", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const guardian = Address.fromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const serviceID = "MultiversXTCSService"; + + const transaction = factory.createTransactionForSettingGuardian({ + sender: sender, + guardianAddress: guardian, + serviceID: serviceID, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "SetGuardian@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@4d756c7469766572735854435353657276696365", + ); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 525500n); + }); + + it("should create 'Transaction' for guarding account", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + const transaction = factory.createTransactionForGuardingAccount({ + sender: sender, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.deepEqual(Buffer.from(transaction.data).toString(), "GuardAccount"); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 368000n); + }); + + it("should create 'Transaction' for unguarding account", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + const transaction = factory.createTransactionForUnguardingAccount({ + sender: sender, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.deepEqual(Buffer.from(transaction.data).toString(), "UnGuardAccount"); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, config.chainID); + assert.equal(transaction.gasLimit, 371000n); + }); +}); diff --git a/src/transactionsFactories/accountTransactionsFactory.ts b/src/transactionsFactories/accountTransactionsFactory.ts new file mode 100644 index 00000000..e65688ef --- /dev/null +++ b/src/transactionsFactories/accountTransactionsFactory.ts @@ -0,0 +1,120 @@ +import { Address } from "../address"; +import { IAddress } from "../interface"; +import { Transaction } from "../transaction"; +import { TransactionBuilder } from "./transactionBuilder"; + +interface IConfig { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitSaveKeyValue: bigint; + gasLimitPersistPerByte: bigint; + gasLimitStorePerByte: bigint; + gasLimitSetGuardian: bigint; + gasLimitGuardAccount: bigint; + gasLimitUnguardAccount: bigint; + extraGasLimitForGuardedTransaction: bigint; +} + +export class AccountTransactionsFactory { + private readonly config: IConfig; + + constructor(options: { config: IConfig }) { + this.config = options.config; + } + + createTransactionForSavingKeyValue(options: { + sender: IAddress; + keyValuePairs: Map; + }): Transaction { + const functionName = "SaveKeyValue"; + const keyValueParts = this.computeDataPartsForSavingKeyValue(options.keyValuePairs); + const dataParts = [functionName, ...keyValueParts]; + const extraGas = this.computeExtraGasForSavingKeyValue(options.keyValuePairs); + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: extraGas, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingGuardian(options: { + sender: IAddress; + guardianAddress: IAddress; + serviceID: string; + }): Transaction { + const dataParts = [ + "SetGuardian", + Address.fromBech32(options.guardianAddress.bech32()).toHex(), + Buffer.from(options.serviceID).toString("hex"), + ]; + + const gasLimit = this.config.gasLimitSetGuardian + this.config.extraGasLimitForGuardedTransaction; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForGuardingAccount(options: { sender: IAddress }): Transaction { + const dataParts = ["GuardAccount"]; + const gasLimit = this.config.gasLimitGuardAccount + this.config.extraGasLimitForGuardedTransaction; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnguardingAccount(options: { sender: IAddress }): Transaction { + const dataParts = ["UnGuardAccount"]; + const gasLimit = this.config.gasLimitUnguardAccount + this.config.extraGasLimitForGuardedTransaction; + + const transaction = new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + transaction.options = 2; + + return transaction; + } + + private computeExtraGasForSavingKeyValue(keyValuePairs: Map): bigint { + let extraGas = 0n; + + keyValuePairs.forEach((value, key) => { + extraGas += + this.config.gasLimitPersistPerByte * BigInt(key.length + value.length) + + this.config.gasLimitStorePerByte * BigInt(value.length); + }); + + return extraGas + this.config.gasLimitSaveKeyValue; + } + + private computeDataPartsForSavingKeyValue(keyValuePairs: Map): string[] { + let dataParts: string[] = []; + + keyValuePairs.forEach((value, key) => { + dataParts.push(...[Buffer.from(key).toString("hex"), Buffer.from(value).toString("hex")]); + }); + + return dataParts; + } +} diff --git a/src/transactionsFactories/delegationTransactionsFactory.ts b/src/transactionsFactories/delegationTransactionsFactory.ts index bc1369b5..39027c6d 100644 --- a/src/transactionsFactories/delegationTransactionsFactory.ts +++ b/src/transactionsFactories/delegationTransactionsFactory.ts @@ -6,7 +6,7 @@ import { ArgSerializer, BigUIntValue, BytesValue, StringValue } from "../smartco import { Transaction } from "../transaction"; import { TransactionBuilder } from "./transactionBuilder"; -interface Config { +interface IConfig { chainID: string; minGasLimit: bigint; gasLimitPerByte: bigint; @@ -27,10 +27,10 @@ interface IValidatorPublicKey { * Use this class to create delegation related transactions like creating a new delegation contract or adding nodes. */ export class DelegationTransactionsFactory { - private readonly config: Config; + private readonly config: IConfig; private readonly argSerializer: ArgSerializer; - constructor(options: { config: Config }) { + constructor(options: { config: IConfig }) { this.config = options.config; this.argSerializer = new ArgSerializer(); } diff --git a/src/transactionsFactories/index.ts b/src/transactionsFactories/index.ts index 6aa27260..fd9df352 100644 --- a/src/transactionsFactories/index.ts +++ b/src/transactionsFactories/index.ts @@ -4,3 +4,4 @@ export * from "./smartContractTransactionsFactory"; export * from "./tokenManagementTransactionsFactory"; export * from "./transactionsFactoryConfig"; export * from "./transferTransactionsFactory"; +export * from "./accountTransactionsFactory"; diff --git a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts index 5b1f771b..d85a01d1 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts @@ -347,4 +347,41 @@ describe("test smart contract transactions factory", function () { assert.deepEqual(transaction, transactionAbiAware); }); + + it("should create 'Transaction' for claiming developer rewards", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + + const transaction = smartContractFactory.createTransactionForClaimingDeveloperRewards({ + sender: sender, + contract: contract, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + assert.equal(Buffer.from(transaction.data).toString(), "ClaimDeveloperRewards"); + assert.equal(transaction.gasLimit, 6000000n); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for changing owner address", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const newOwner = Address.fromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = smartContractFactory.createTransactionForChangingOwnerAddress({ + sender: sender, + contract: contract, + newOwner: newOwner, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + assert.equal( + Buffer.from(transaction.data).toString(), + "ChangeOwnerAddress@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); + assert.equal(transaction.gasLimit, 6000000n); + assert.equal(transaction.value, 0n); + }); }); diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index 832d8ae3..d77db52b 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -10,10 +10,12 @@ import { byteArrayToHex, utf8ToHex } from "../utils.codec"; import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; import { TransactionBuilder } from "./transactionBuilder"; -interface Config { +interface IConfig { chainID: string; minGasLimit: bigint; gasLimitPerByte: bigint; + gasLimitClaimDeveloperRewards: bigint; + gasLimitChangeOwnerAddress: bigint; } interface IAbi { @@ -26,12 +28,12 @@ interface IAbi { * Use this class to create transactions to deploy, call or upgrade a smart contract. */ export class SmartContractTransactionsFactory { - private readonly config: Config; + private readonly config: IConfig; private readonly abi?: IAbi; private readonly tokenComputer: TokenComputer; private readonly dataArgsBuilder: TokenTransfersDataBuilder; - constructor(options: { config: Config; abi?: IAbi }) { + constructor(options: { config: IConfig; abi?: IAbi }) { this.config = options.config; this.abi = options.abi; this.tokenComputer = new TokenComputer(); @@ -157,6 +159,36 @@ export class SmartContractTransactionsFactory { }).build(); } + createTransactionForClaimingDeveloperRewards(options: { sender: IAddress; contract: IAddress }): Transaction { + const dataParts = ["ClaimDeveloperRewards"]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.contract, + dataParts: dataParts, + gasLimit: this.config.gasLimitClaimDeveloperRewards, + addDataMovementGas: false, + }).build(); + } + + createTransactionForChangingOwnerAddress(options: { + sender: IAddress; + contract: IAddress; + newOwner: IAddress; + }): Transaction { + const dataParts = ["ChangeOwnerAddress", Address.fromBech32(options.newOwner.bech32()).toHex()]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.contract, + dataParts: dataParts, + gasLimit: this.config.gasLimitChangeOwnerAddress, + addDataMovementGas: false, + }).build(); + } + private argsToDataParts(args: any[], endpoint?: EndpointDefinition): string[] { if (endpoint) { const typedArgs = NativeSerializer.nativeToTypedValues(args, endpoint); diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts index 86662c00..a2420df5 100644 --- a/src/transactionsFactories/tokenManagementTransactionsFactory.ts +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -6,7 +6,7 @@ import { AddressValue, ArgSerializer, BigUIntValue, BytesValue, StringValue } fr import { Transaction } from "../transaction"; import { TransactionBuilder } from "./transactionBuilder"; -interface Config { +interface IConfig { chainID: string; minGasLimit: bigint; gasLimitPerByte: bigint; @@ -32,12 +32,12 @@ type RegisterAndSetAllRolesTokenType = "NFT" | "SFT" | "META" | "FNG"; * Use this class to create token management transactions like issuing ESDTs, creating NFTs, setting roles, etc. */ export class TokenManagementTransactionsFactory { - private readonly config: Config; + private readonly config: IConfig; private readonly argSerializer: ArgSerializer; private readonly trueAsString: string; private readonly falseAsString: string; - constructor(options: { config: Config }) { + constructor(options: { config: IConfig }) { this.config = options.config; this.argSerializer = new ArgSerializer(); this.trueAsString = "true"; @@ -412,7 +412,7 @@ export class TokenManagementTransactionsFactory { // Note that the following is an approximation (a reasonable one): const nftData = options.name + options.hash + options.attributes + options.uris.join(""); - const storageGasLimit = this.config.gasLimitPerByte + BigInt(nftData.length); + const storageGasLimit = this.config.gasLimitStorePerByte + BigInt(nftData.length); return new TransactionBuilder({ config: this.config, diff --git a/src/transactionsFactories/transactionsFactoryConfig.ts b/src/transactionsFactories/transactionsFactoryConfig.ts index 4de6201c..5604774f 100644 --- a/src/transactionsFactories/transactionsFactoryConfig.ts +++ b/src/transactionsFactories/transactionsFactoryConfig.ts @@ -29,6 +29,14 @@ export class TransactionsFactoryConfig { gasLimitESDTTransfer: bigint; gasLimitESDTNFTTransfer: bigint; gasLimitMultiESDTNFTTransfer: bigint; + gasLimitSaveKeyValue: bigint; + gasLimitPersistPerByte: bigint; + gasLimitSetGuardian: bigint; + gasLimitGuardAccount: bigint; + gasLimitUnguardAccount: bigint; + extraGasLimitForGuardedTransaction: bigint; + gasLimitClaimDeveloperRewards: bigint; + gasLimitChangeOwnerAddress: bigint; constructor(options: { chainID: string }) { // General-purpose configuration @@ -50,7 +58,7 @@ export class TransactionsFactoryConfig { this.gasLimitEsdtNftUpdateAttributes = 1000000n; this.gasLimitEsdtNftAddQuantity = 1000000n; this.gasLimitEsdtNftBurn = 1000000n; - this.gasLimitStorePerByte = 50000n; + this.gasLimitStorePerByte = 10000n; this.issueCost = 50000000000000000n; // Configuration for delegation operations @@ -62,9 +70,21 @@ export class TransactionsFactoryConfig { this.additionalGasLimitPerValidatorNode = 6000000n; this.additionalGasLimitForDelegationOperations = 10000000n; + // Configuration for account operations + this.gasLimitSaveKeyValue = 100000n; + this.gasLimitPersistPerByte = 1000n; + this.gasLimitSetGuardian = 250000n; + this.gasLimitGuardAccount = 250000n; + this.gasLimitUnguardAccount = 250000n; + this.extraGasLimitForGuardedTransaction = 50000n; + // Configuration for token transfers this.gasLimitESDTTransfer = 200000n; this.gasLimitESDTNFTTransfer = 200000n; this.gasLimitMultiESDTNFTTransfer = 200000n; + + // Configuration for smart contract operations + this.gasLimitClaimDeveloperRewards = 6000000n; + this.gasLimitChangeOwnerAddress = 6000000n; } } From c75225559172044c7bf04bcd466c1e787cd69dc0 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 16 Apr 2024 16:40:11 +0300 Subject: [PATCH 05/16] bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e075b5d4..b74e4578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.0.1", + "version": "13.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.0.1", + "version": "13.1.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 4a38380f..df883f36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.0.1", + "version": "13.1.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", From 8ea875026196e5b47c8aa0b5b156435a0a4ad945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 16 Apr 2024 17:21:43 +0300 Subject: [PATCH 06/16] Additional unit tests. --- src/testdata/adder.abi.json | 27 ++++++++++++++----- .../smartContractTransactionsFactory.spec.ts | 25 +++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/testdata/adder.abi.json b/src/testdata/adder.abi.json index 88d5bf13..7aba85f5 100644 --- a/src/testdata/adder.abi.json +++ b/src/testdata/adder.abi.json @@ -2,18 +2,19 @@ "buildInfo": { "rustc": { "version": "1.71.0-nightly", - "commitHash": "7f94b314cead7059a71a265a8b64905ef2511796", - "commitDate": "2023-04-23", + "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", + "commitDate": "2023-05-25", "channel": "Nightly", - "short": "rustc 1.71.0-nightly (7f94b314c 2023-04-23)" + "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" }, "contractCrate": { "name": "adder", - "version": "0.0.0" + "version": "0.0.0", + "gitVersion": "v0.45.2.1-reproducible-169-g37d970c" }, "framework": { "name": "multiversx-sc", - "version": "0.41.3" + "version": "0.47.2" } }, "docs": [ @@ -41,12 +42,26 @@ } ] }, + { + "name": "upgrade", + "mutability": "mutable", + "inputs": [ + { + "name": "new_value", + "type": "BigUint" + } + ], + "outputs": [] + }, { "docs": [ "Add desired amount to the storage variable." ], "name": "add", "mutability": "mutable", + "payableInTokens": [ + "*" + ], "inputs": [ { "name": "value", @@ -56,7 +71,7 @@ "outputs": [] } ], - "events": [], + "esdtAttributes": [], "hasCallback": false, "types": {} } diff --git a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts index 5b1f771b..ee920aed 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts @@ -321,7 +321,7 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const gasLimit = 6000000n; - const args = [new U32Value(0)]; + const args = [new U32Value(7)]; const transaction = smartContractFactory.createTransactionForUpgrade({ sender: sender, @@ -341,10 +341,31 @@ describe("test smart contract transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); - assert.isTrue(Buffer.from(transaction.data!).toString().startsWith("upgradeContract@")); + assert.equal(Buffer.from(transaction.data!).toString(), `upgradeContract@${adderByteCode}@0504@07`); assert.equal(transaction.gasLimit, gasLimit); assert.equal(transaction.value, 0n); assert.deepEqual(transaction, transactionAbiAware); }); + + it("should create 'Transaction' for upgrade, when ABI is available, but it doesn't contain a definition for 'upgrade'", async function () { + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + // Remove all endpoints (for the sake of the test). + abi.endpoints.length = 0; + + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abi, + }); + + const transaction = factory.createTransactionForUpgrade({ + sender: Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + contract: Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"), + bytecode: adderByteCode.valueOf(), + gasLimit: 6000000n, + arguments: [new U32Value(7)], + }); + + assert.equal(Buffer.from(transaction.data!).toString(), `upgradeContract@${adderByteCode}@0504@07`); + }); }); From 5136ee3643bb395d4a50611b190edf9d837f48d2 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 17 Apr 2024 11:22:45 +0300 Subject: [PATCH 07/16] fixes after review --- .../accountTransactionsFactory.spec.ts | 16 ++--- .../accountTransactionsFactory.ts | 60 ++++++++----------- .../transactionsFactoryConfig.ts | 2 - 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/src/transactionsFactories/accountTransactionsFactory.spec.ts b/src/transactionsFactories/accountTransactionsFactory.spec.ts index 5db44df3..6dc4ec8e 100644 --- a/src/transactionsFactories/accountTransactionsFactory.spec.ts +++ b/src/transactionsFactories/accountTransactionsFactory.spec.ts @@ -9,7 +9,7 @@ describe("test account transactions factory", function () { it("should create 'Transaction' for saving key value", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const keyValuePairs = new Map([[Buffer.from("6b657930", "hex"), Buffer.from("76616c756530", "hex")]]); + const keyValuePairs = new Map([[Buffer.from("key0"), Buffer.from("value0")]]); const transaction = factory.createTransactionForSavingKeyValue({ sender: sender, @@ -18,7 +18,7 @@ describe("test account transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.deepEqual(Buffer.from(transaction.data).toString(), "SaveKeyValue@6b657930@76616c756530"); + assert.equal(Buffer.from(transaction.data).toString(), "SaveKeyValue@6b657930@76616c756530"); assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); assert.equal(transaction.gasLimit, 271000n); @@ -37,13 +37,13 @@ describe("test account transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.deepEqual( + assert.equal( Buffer.from(transaction.data).toString(), "SetGuardian@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@4d756c7469766572735854435353657276696365", ); assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); - assert.equal(transaction.gasLimit, 525500n); + assert.equal(transaction.gasLimit, 475500n); }); it("should create 'Transaction' for guarding account", async function () { @@ -55,10 +55,10 @@ describe("test account transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.deepEqual(Buffer.from(transaction.data).toString(), "GuardAccount"); + assert.equal(Buffer.from(transaction.data).toString(), "GuardAccount"); assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); - assert.equal(transaction.gasLimit, 368000n); + assert.equal(transaction.gasLimit, 318000n); }); it("should create 'Transaction' for unguarding account", async function () { @@ -70,9 +70,9 @@ describe("test account transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.deepEqual(Buffer.from(transaction.data).toString(), "UnGuardAccount"); + assert.equal(Buffer.from(transaction.data).toString(), "UnGuardAccount"); assert.equal(transaction.value, 0n); assert.equal(transaction.chainID, config.chainID); - assert.equal(transaction.gasLimit, 371000n); + assert.equal(transaction.gasLimit, 321000n); }); }); diff --git a/src/transactionsFactories/accountTransactionsFactory.ts b/src/transactionsFactories/accountTransactionsFactory.ts index e65688ef..01784bd2 100644 --- a/src/transactionsFactories/accountTransactionsFactory.ts +++ b/src/transactionsFactories/accountTransactionsFactory.ts @@ -13,7 +13,6 @@ interface IConfig { gasLimitSetGuardian: bigint; gasLimitGuardAccount: bigint; gasLimitUnguardAccount: bigint; - extraGasLimitForGuardedTransaction: bigint; } export class AccountTransactionsFactory { @@ -42,6 +41,28 @@ export class AccountTransactionsFactory { }).build(); } + private computeExtraGasForSavingKeyValue(keyValuePairs: Map): bigint { + let extraGas = 0n; + + keyValuePairs.forEach((value, key) => { + extraGas += + this.config.gasLimitPersistPerByte * BigInt(key.length + value.length) + + this.config.gasLimitStorePerByte * BigInt(value.length); + }); + + return extraGas + this.config.gasLimitSaveKeyValue; + } + + private computeDataPartsForSavingKeyValue(keyValuePairs: Map): string[] { + const dataParts: string[] = []; + + keyValuePairs.forEach((value, key) => { + dataParts.push(...[Buffer.from(key).toString("hex"), Buffer.from(value).toString("hex")]); + }); + + return dataParts; + } + createTransactionForSettingGuardian(options: { sender: IAddress; guardianAddress: IAddress; @@ -53,68 +74,39 @@ export class AccountTransactionsFactory { Buffer.from(options.serviceID).toString("hex"), ]; - const gasLimit = this.config.gasLimitSetGuardian + this.config.extraGasLimitForGuardedTransaction; - return new TransactionBuilder({ config: this.config, sender: options.sender, receiver: options.sender, dataParts: dataParts, - gasLimit: gasLimit, + gasLimit: this.config.gasLimitSetGuardian, addDataMovementGas: true, }).build(); } createTransactionForGuardingAccount(options: { sender: IAddress }): Transaction { const dataParts = ["GuardAccount"]; - const gasLimit = this.config.gasLimitGuardAccount + this.config.extraGasLimitForGuardedTransaction; return new TransactionBuilder({ config: this.config, sender: options.sender, receiver: options.sender, dataParts: dataParts, - gasLimit: gasLimit, + gasLimit: this.config.gasLimitGuardAccount, addDataMovementGas: true, }).build(); } createTransactionForUnguardingAccount(options: { sender: IAddress }): Transaction { const dataParts = ["UnGuardAccount"]; - const gasLimit = this.config.gasLimitUnguardAccount + this.config.extraGasLimitForGuardedTransaction; - const transaction = new TransactionBuilder({ + return new TransactionBuilder({ config: this.config, sender: options.sender, receiver: options.sender, dataParts: dataParts, - gasLimit: gasLimit, + gasLimit: this.config.gasLimitUnguardAccount, addDataMovementGas: true, }).build(); - transaction.options = 2; - - return transaction; - } - - private computeExtraGasForSavingKeyValue(keyValuePairs: Map): bigint { - let extraGas = 0n; - - keyValuePairs.forEach((value, key) => { - extraGas += - this.config.gasLimitPersistPerByte * BigInt(key.length + value.length) + - this.config.gasLimitStorePerByte * BigInt(value.length); - }); - - return extraGas + this.config.gasLimitSaveKeyValue; - } - - private computeDataPartsForSavingKeyValue(keyValuePairs: Map): string[] { - let dataParts: string[] = []; - - keyValuePairs.forEach((value, key) => { - dataParts.push(...[Buffer.from(key).toString("hex"), Buffer.from(value).toString("hex")]); - }); - - return dataParts; } } diff --git a/src/transactionsFactories/transactionsFactoryConfig.ts b/src/transactionsFactories/transactionsFactoryConfig.ts index 5604774f..5614c13f 100644 --- a/src/transactionsFactories/transactionsFactoryConfig.ts +++ b/src/transactionsFactories/transactionsFactoryConfig.ts @@ -34,7 +34,6 @@ export class TransactionsFactoryConfig { gasLimitSetGuardian: bigint; gasLimitGuardAccount: bigint; gasLimitUnguardAccount: bigint; - extraGasLimitForGuardedTransaction: bigint; gasLimitClaimDeveloperRewards: bigint; gasLimitChangeOwnerAddress: bigint; @@ -76,7 +75,6 @@ export class TransactionsFactoryConfig { this.gasLimitSetGuardian = 250000n; this.gasLimitGuardAccount = 250000n; this.gasLimitUnguardAccount = 250000n; - this.extraGasLimitForGuardedTransaction = 50000n; // Configuration for token transfers this.gasLimitESDTTransfer = 200000n; From 084873c277ddea3ee529df1e2d2ea5c680b63a57 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 17 Apr 2024 11:58:16 +0300 Subject: [PATCH 08/16] make field mandatory --- .../tokenManagementTransactionIntentsFactory.spec.ts | 1 + src/transactionsFactories/tokenManagementTransactionsFactory.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts index dd39f03e..6dee6416 100644 --- a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -140,6 +140,7 @@ describe("test token management transactions factory", () => { tokenIdentifier: "FRANK-11ce3e", addRoleLocalMint: true, addRoleLocalBurn: false, + addRoleESDTTransferRole: false, }); assert.deepEqual( diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts index de6afde5..3bfeac3b 100644 --- a/src/transactionsFactories/tokenManagementTransactionsFactory.ts +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -299,7 +299,7 @@ export class TokenManagementTransactionsFactory { tokenIdentifier: string; addRoleLocalMint: boolean; addRoleLocalBurn: boolean; - addRoleESDTTransferRole?: boolean; + addRoleESDTTransferRole: boolean; }): Transaction { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; From 14bbf753a79c67316bfb2dd3f7dc70f396ff0914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 17 Apr 2024 14:02:41 +0300 Subject: [PATCH 09/16] Sketch test (debugging). --- src/smartcontracts/nativeSerializer.spec.ts | 43 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/smartcontracts/nativeSerializer.spec.ts b/src/smartcontracts/nativeSerializer.spec.ts index 2a8cb233..1651c245 100644 --- a/src/smartcontracts/nativeSerializer.spec.ts +++ b/src/smartcontracts/nativeSerializer.spec.ts @@ -438,6 +438,47 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[2].getType(), enumType); assert.deepEqual(typedValues[2].valueOf(), { name: 'Something', fields: [new Address('erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha')] }); assert.deepEqual(typedValues[3].getType(), enumType); - assert.deepEqual(typedValues[3].valueOf(), { name: 'Else', fields: [new BigNumber(42), new BigNumber(43)] }); + assert.deepEqual(typedValues[3].valueOf(), { name: "Else", fields: [new BigNumber(42), new BigNumber(43)] }); + }); + + it.only("issue 435", async () => { + const abi = AbiRegistry.create({ + endpoints: [ + { + name: "foo", + inputs: [ + { + type: "optional>", + }, + ], + outputs: [], + }, + ], + types: { + MyEnum: { + type: "enum", + variants: [ + { + name: "a", + discriminant: 0, + }, + { + name: "b", + discriminant: 1, + }, + { + name: "c", + discriminant: 2, + }, + ], + }, + }, + }); + + const endpoint = abi.getEndpoint("foo"); + + NativeSerializer.nativeToTypedValues([], endpoint); + NativeSerializer.nativeToTypedValues([["a"]], endpoint); + NativeSerializer.nativeToTypedValues([[0]], endpoint); }); }); From b24c76bc3d3df6d435534066e229616f9f3c3854 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 17 Apr 2024 14:12:26 +0300 Subject: [PATCH 10/16] formatting --- src/globals.ts | 4 +-- src/relayedTransactionV1Builder.ts | 57 ++++++++++++++++++++---------- src/relayedTransactionV2Builder.ts | 16 ++++++--- src/tokenTransferBuilders.ts | 22 ++++++------ src/transactionPayload.ts | 2 +- 5 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/globals.ts b/src/globals.ts index ebc4e750..6169db45 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,3 +1,3 @@ -if ( !global.Buffer ) { - global.Buffer = require('buffer').Buffer; +if (!global.Buffer) { + global.Buffer = require("buffer").Buffer; } diff --git a/src/relayedTransactionV1Builder.ts b/src/relayedTransactionV1Builder.ts index c4871b61..52a48262 100644 --- a/src/relayedTransactionV1Builder.ts +++ b/src/relayedTransactionV1Builder.ts @@ -64,7 +64,7 @@ export class RelayedTransactionV1Builder { * (optional) Sets the version of the relayed transaction * * @param relayedTxVersion - */ + */ setRelayedTransactionVersion(relayedTxVersion: TransactionVersion): RelayedTransactionV1Builder { this.relayedTransactionVersion = relayedTxVersion; return this; @@ -74,7 +74,7 @@ export class RelayedTransactionV1Builder { * (optional) Sets the options of the relayed transaction * * @param relayedTxOptions - */ + */ setRelayedTransactionOptions(relayedTxOptions: TransactionOptions): RelayedTransactionV1Builder { this.relayedTransactionOptions = relayedTxOptions; return this; @@ -97,7 +97,12 @@ export class RelayedTransactionV1Builder { * @return Transaction */ build(): Transaction { - if (!this.innerTransaction || !this.netConfig || !this.relayerAddress || !this.innerTransaction.getSignature()) { + if ( + !this.innerTransaction || + !this.netConfig || + !this.relayerAddress || + !this.innerTransaction.getSignature() + ) { throw new ErrInvalidRelayedV1BuilderArguments(); } @@ -105,7 +110,10 @@ export class RelayedTransactionV1Builder { const data = `relayedTx@${Buffer.from(serializedTransaction).toString("hex")}`; const payload = new TransactionPayload(data); - const gasLimit = this.netConfig.MinGasLimit + this.netConfig.GasPerDataByte * payload.length() + this.innerTransaction.getGasLimit().valueOf(); + const gasLimit = + this.netConfig.MinGasLimit + + this.netConfig.GasPerDataByte * payload.length() + + this.innerTransaction.getGasLimit().valueOf(); let relayedTransaction = new Transaction({ nonce: this.relayerNonce, sender: this.relayerAddress, @@ -132,21 +140,32 @@ export class RelayedTransactionV1Builder { } const txObject = { - "nonce": this.innerTransaction.getNonce().valueOf(), - "sender": new Address(this.innerTransaction.getSender().bech32()).pubkey().toString("base64"), - "receiver": new Address(this.innerTransaction.getReceiver().bech32()).pubkey().toString("base64"), - "value": BigInt(this.innerTransaction.getValue().toString()), - "gasPrice": this.innerTransaction.getGasPrice().valueOf(), - "gasLimit": this.innerTransaction.getGasLimit().valueOf(), - "data": this.innerTransaction.getData().valueOf().toString("base64"), - "signature": this.innerTransaction.getSignature().toString("base64"), - "chainID": Buffer.from(this.innerTransaction.getChainID().valueOf()).toString("base64"), - "version": this.innerTransaction.getVersion().valueOf(), - "options": this.innerTransaction.getOptions().valueOf() == 0 ? undefined : this.innerTransaction.getOptions().valueOf(), - "guardian": this.innerTransaction.getGuardian().bech32() ? new Address(this.innerTransaction.getGuardian().bech32()).pubkey().toString("base64") : undefined, - "guardianSignature": this.innerTransaction.getGuardianSignature().toString("hex") ? this.innerTransaction.getGuardianSignature().toString("base64") : undefined, - "sndUserName": this.innerTransaction.getSenderUsername() ? Buffer.from(this.innerTransaction.getSenderUsername()).toString("base64") : undefined, - "rcvUserName": this.innerTransaction.getReceiverUsername() ? Buffer.from(this.innerTransaction.getReceiverUsername()).toString("base64") : undefined, + nonce: this.innerTransaction.getNonce().valueOf(), + sender: new Address(this.innerTransaction.getSender().bech32()).pubkey().toString("base64"), + receiver: new Address(this.innerTransaction.getReceiver().bech32()).pubkey().toString("base64"), + value: BigInt(this.innerTransaction.getValue().toString()), + gasPrice: this.innerTransaction.getGasPrice().valueOf(), + gasLimit: this.innerTransaction.getGasLimit().valueOf(), + data: this.innerTransaction.getData().valueOf().toString("base64"), + signature: this.innerTransaction.getSignature().toString("base64"), + chainID: Buffer.from(this.innerTransaction.getChainID().valueOf()).toString("base64"), + version: this.innerTransaction.getVersion().valueOf(), + options: + this.innerTransaction.getOptions().valueOf() == 0 + ? undefined + : this.innerTransaction.getOptions().valueOf(), + guardian: this.innerTransaction.getGuardian().bech32() + ? new Address(this.innerTransaction.getGuardian().bech32()).pubkey().toString("base64") + : undefined, + guardianSignature: this.innerTransaction.getGuardianSignature().toString("hex") + ? this.innerTransaction.getGuardianSignature().toString("base64") + : undefined, + sndUserName: this.innerTransaction.getSenderUsername() + ? Buffer.from(this.innerTransaction.getSenderUsername()).toString("base64") + : undefined, + rcvUserName: this.innerTransaction.getReceiverUsername() + ? Buffer.from(this.innerTransaction.getReceiverUsername()).toString("base64") + : undefined, }; return JSONbig.stringify(txObject); diff --git a/src/relayedTransactionV2Builder.ts b/src/relayedTransactionV2Builder.ts index a213c541..b5b01b1a 100644 --- a/src/relayedTransactionV2Builder.ts +++ b/src/relayedTransactionV2Builder.ts @@ -76,7 +76,13 @@ export class RelayedTransactionV2Builder { * @return Transaction */ build(): Transaction { - if (!this.innerTransaction || !this.innerTransactionGasLimit || !this.relayerAddress || !this.netConfig || !this.innerTransaction.getSignature()) { + if ( + !this.innerTransaction || + !this.innerTransactionGasLimit || + !this.relayerAddress || + !this.netConfig || + !this.innerTransaction.getSignature() + ) { throw new ErrInvalidRelayedV2BuilderArguments(); } if (this.innerTransaction.getGasLimit() != 0) { @@ -87,7 +93,7 @@ export class RelayedTransactionV2Builder { new AddressValue(this.innerTransaction.getReceiver()), new U64Value(this.innerTransaction.getNonce().valueOf()), new BytesValue(this.innerTransaction.getData().valueOf()), - new BytesValue(this.innerTransaction.getSignature()) + new BytesValue(this.innerTransaction.getSignature()), ]); const data = `relayedTxV2@${argumentsString}`; @@ -98,11 +104,13 @@ export class RelayedTransactionV2Builder { receiver: this.innerTransaction.getSender(), value: 0, gasLimit: - this.innerTransactionGasLimit.valueOf() + this.netConfig.MinGasLimit + this.netConfig.GasPerDataByte * payload.length(), + this.innerTransactionGasLimit.valueOf() + + this.netConfig.MinGasLimit + + this.netConfig.GasPerDataByte * payload.length(), data: payload, chainID: this.netConfig.ChainID, version: this.innerTransaction.getVersion(), - options: this.innerTransaction.getOptions() + options: this.innerTransaction.getOptions(), }); if (this.relayerNonce) { diff --git a/src/tokenTransferBuilders.ts b/src/tokenTransferBuilders.ts index 1c6f7009..7760ca7d 100644 --- a/src/tokenTransferBuilders.ts +++ b/src/tokenTransferBuilders.ts @@ -56,7 +56,7 @@ export class ESDTNFTTransferPayloadBuilder { // The transferred quantity new BigUIntValue(this.payment.valueOf()), // The destination address - new AddressValue(this.destination) + new AddressValue(this.destination), ]; let { argumentsString } = new ArgSerializer().valuesToString(args); @@ -87,18 +87,20 @@ export class MultiESDTNFTTransferPayloadBuilder { // The destination address new AddressValue(this.destination), // Number of tokens - new U16Value(this.payments.length) + new U16Value(this.payments.length), ]; for (const payment of this.payments) { - args.push(...[ - // The token identifier - BytesValue.fromUTF8(payment.tokenIdentifier), - // The nonce of the token - new U64Value(payment.nonce), - // The transfered quantity - new BigUIntValue(payment.valueOf()) - ]); + args.push( + ...[ + // The token identifier + BytesValue.fromUTF8(payment.tokenIdentifier), + // The nonce of the token + new U64Value(payment.nonce), + // The transfered quantity + new BigUIntValue(payment.valueOf()), + ], + ); } let { argumentsString } = new ArgSerializer().valuesToString(args); diff --git a/src/transactionPayload.ts b/src/transactionPayload.ts index 8cd07ae2..0f346d88 100644 --- a/src/transactionPayload.ts +++ b/src/transactionPayload.ts @@ -53,7 +53,7 @@ export class TransactionPayload { } getRawArguments(): Buffer[] { - return this.getEncodedArguments().map(argument => Buffer.from(argument, "hex")); + return this.getEncodedArguments().map((argument) => Buffer.from(argument, "hex")); } /** From 7178a886f559199a6c0d7759168d0ad67a2a498b Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 17 Apr 2024 14:41:12 +0300 Subject: [PATCH 11/16] formatting --- src/smartcontracts/codeMetadata.ts | 23 ++++--- src/smartcontracts/interactionChecker.ts | 8 ++- src/smartcontracts/interface.ts | 2 +- src/smartcontracts/query.ts | 10 +-- src/smartcontracts/resultsParser.ts | 62 +++++++++++------- src/smartcontracts/smartContract.ts | 80 +++++++++++++++++------- 6 files changed, 124 insertions(+), 61 deletions(-) diff --git a/src/smartcontracts/codeMetadata.ts b/src/smartcontracts/codeMetadata.ts index 2133e66e..9d2fa2bc 100644 --- a/src/smartcontracts/codeMetadata.ts +++ b/src/smartcontracts/codeMetadata.ts @@ -11,13 +11,13 @@ export class CodeMetadata { static ByteZero = { Upgradeable: 1, Reserved2: 2, - Readable: 4 + Readable: 4, }; static ByteOne = { Reserved1: 1, Payable: 2, - PayableBySc: 4 + PayableBySc: 4, }; /** @@ -28,11 +28,16 @@ export class CodeMetadata { * @param payable Whether the contract is payable * @param payableBySc Whether the contract is payable by other smart contracts */ - constructor(upgradeable: boolean = true, readable: boolean = false, payable: boolean = false, payableBySc: boolean = false) { + constructor( + upgradeable: boolean = true, + readable: boolean = false, + payable: boolean = false, + payableBySc: boolean = false, + ) { this.upgradeable = upgradeable; this.readable = readable; this.payable = payable; - this.payableBySc = payableBySc + this.payableBySc = payableBySc; } static fromBytes(bytes: Uint8Array): CodeMetadata { @@ -44,7 +49,7 @@ export class CodeMetadata { */ static fromBuffer(buffer: Buffer): CodeMetadata { if (buffer.length < this.codeMetadataLength) { - throw new Error('Buffer is too short.'); + throw new Error("Buffer is too short."); } const byteZero = buffer[0]; @@ -124,14 +129,16 @@ export class CodeMetadata { upgradeable: this.upgradeable, readable: this.readable, payable: this.payable, - payableBySc: this.payableBySc + payableBySc: this.payableBySc, }; } equals(other: CodeMetadata): boolean { - return this.upgradeable == other.upgradeable && + return ( + this.upgradeable == other.upgradeable && this.readable == other.readable && this.payable == other.payable && - this.payableBySc == other.payableBySc; + this.payableBySc == other.payableBySc + ); } } diff --git a/src/smartcontracts/interactionChecker.ts b/src/smartcontracts/interactionChecker.ts index d383d54f..ac122c55 100644 --- a/src/smartcontracts/interactionChecker.ts +++ b/src/smartcontracts/interactionChecker.ts @@ -32,7 +32,9 @@ export class InteractionChecker { let numActualArguments = actualArguments.length; if (numFormalArguments != numActualArguments) { - throw new errors.ErrContractInteraction(`bad arguments, expected: ${numFormalArguments}, got: ${numActualArguments}`); + throw new errors.ErrContractInteraction( + `bad arguments, expected: ${numFormalArguments}, got: ${numActualArguments}`, + ); } // TODO: discuss again, possibly redesign the handling of covariance / contravariance. @@ -45,7 +47,9 @@ export class InteractionChecker { let ok = expectedType.isAssignableFrom(actualType); if (!ok) { - throw new errors.ErrContractInteraction(`type mismatch at index ${i}, expected: ${expectedType}, got: ${actualType}`); + throw new errors.ErrContractInteraction( + `type mismatch at index ${i}, expected: ${expectedType}, got: ${actualType}`, + ); } } } diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index 9b935643..b474ad8e 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -65,7 +65,7 @@ export interface QueryArguments { func: IContractFunction; args?: TypedValue[]; value?: ITransactionValue; - caller?: IAddress + caller?: IAddress; } export interface TypedOutcomeBundle { diff --git a/src/smartcontracts/query.ts b/src/smartcontracts/query.ts index 3db54519..7249ebd6 100644 --- a/src/smartcontracts/query.ts +++ b/src/smartcontracts/query.ts @@ -12,11 +12,11 @@ export class Query { value: ITransactionValue; constructor(obj: { - caller?: IAddress, - address: IAddress, - func: IContractFunction, - args?: TypedValue[], - value?: ITransactionValue + caller?: IAddress; + address: IAddress; + func: IContractFunction; + args?: TypedValue[]; + value?: ITransactionValue; }) { this.caller = obj.caller || Address.empty(); this.address = obj.address; diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index b0323c9b..3b15bc5d 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -20,11 +20,11 @@ import { Type, TypedValue } from "./typesystem"; enum WellKnownEvents { OnTransactionCompleted = "completedTxEvent", OnSignalError = "signalError", - OnWriteLog = "writeLog" + OnWriteLog = "writeLog", } enum WellKnownTopics { - TooMuchGas = "@too much gas provided for processing" + TooMuchGas = "@too much gas provided for processing", } interface IResultsParserOptions { @@ -55,7 +55,7 @@ interface IArgsSerializer { // TODO: perhaps move default construction options to a factory (ResultsParserFactory), instead of referencing them in the constructor // (postpone as much as possible, breaking change) const defaultResultsParserOptions: IResultsParserOptions = { - argsSerializer: new ArgSerializer() + argsSerializer: new ArgSerializer(), }; /** @@ -78,7 +78,10 @@ export class ResultsParser { /** * Legacy method, use "SmartContractQueriesController.parseQueryResponse()" instead. */ - parseQueryResponse(queryResponse: IContractQueryResponse, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle { + parseQueryResponse( + queryResponse: IContractQueryResponse, + endpoint: { output: IParameterDefinition[] }, + ): TypedOutcomeBundle { let parts = queryResponse.getReturnDataParts(); let values = this.argsSerializer.buffersToValues(parts, endpoint.output); let returnCode = new ReturnCode(queryResponse.returnCode.toString()); @@ -90,7 +93,7 @@ export class ResultsParser { firstValue: values[0], secondValue: values[1], thirdValue: values[2], - lastValue: values[values.length - 1] + lastValue: values[values.length - 1], }; } @@ -98,12 +101,12 @@ export class ResultsParser { * Legacy method, use "SmartContractQueriesController.parseQueryResponse()" instead. */ parseUntypedQueryResponse(queryResponse: IContractQueryResponse): UntypedOutcomeBundle { - let returnCode = new ReturnCode(queryResponse.returnCode.toString()) + let returnCode = new ReturnCode(queryResponse.returnCode.toString()); return { returnCode: returnCode, returnMessage: queryResponse.returnMessage, - values: queryResponse.getReturnDataParts() + values: queryResponse.getReturnDataParts(), }; } @@ -130,7 +133,7 @@ export class ResultsParser { firstValue: values[0], secondValue: values[1], thirdValue: values[2], - lastValue: values[values.length - 1] + lastValue: values[values.length - 1], }; } @@ -198,7 +201,7 @@ export class ResultsParser { sender: transaction.sender.bech32(), receiver: transaction.receiver.bech32(), data: transaction.data.toString("base64"), - value: transaction.value.toString() + value: transaction.value.toString(), }); } @@ -210,7 +213,7 @@ export class ResultsParser { return { returnCode: ReturnCode.None, returnMessage: ReturnCode.None.toString(), - values: [] + values: [], }; } @@ -223,7 +226,7 @@ export class ResultsParser { return { returnCode: ReturnCode.OutOfFunds, returnMessage: transaction.receipt.data, - values: [] + values: [], }; } @@ -234,7 +237,9 @@ export class ResultsParser { } private createBundleOnEasilyFoundResultWithReturnData(results: IContractResults): UntypedOutcomeBundle | null { - let resultItemWithReturnData = results.items.find(item => item.nonce.valueOf() != 0 && item.data.startsWith("@")); + let resultItemWithReturnData = results.items.find( + (item) => item.nonce.valueOf() != 0 && item.data.startsWith("@"), + ); if (!resultItemWithReturnData) { return null; } @@ -245,7 +250,7 @@ export class ResultsParser { return { returnCode: returnCode, returnMessage: returnMessage, - values: returnDataParts + values: returnDataParts, }; } @@ -262,14 +267,16 @@ export class ResultsParser { return { returnCode: returnCode, returnMessage: returnMessage, - values: returnDataParts + values: returnDataParts, }; } private createBundleOnTooMuchGasWarning(logs: ITransactionLogs): UntypedOutcomeBundle | null { let eventTooMuchGas = logs.findSingleOrNoneEvent( WellKnownEvents.OnWriteLog, - event => event.findFirstOrNoneTopic(topic => topic.toString().startsWith(WellKnownTopics.TooMuchGas)) != undefined + (event) => + event.findFirstOrNoneTopic((topic) => topic.toString().startsWith(WellKnownTopics.TooMuchGas)) != + undefined, ); if (!eventTooMuchGas) { @@ -287,12 +294,15 @@ export class ResultsParser { }; } - private createBundleOnWriteLogWhereFirstTopicEqualsAddress(logs: ITransactionLogs, address: IAddress): UntypedOutcomeBundle | null { + private createBundleOnWriteLogWhereFirstTopicEqualsAddress( + logs: ITransactionLogs, + address: IAddress, + ): UntypedOutcomeBundle | null { let hexAddress = new Address(address.bech32()).hex(); let eventWriteLogWhereTopicIsSender = logs.findSingleOrNoneEvent( WellKnownEvents.OnWriteLog, - event => event.findFirstOrNoneTopic(topic => topic.hex() == hexAddress) != undefined + (event) => event.findFirstOrNoneTopic((topic) => topic.hex() == hexAddress) != undefined, ); if (!eventWriteLogWhereTopicIsSender) { @@ -305,23 +315,29 @@ export class ResultsParser { return { returnCode: returnCode, returnMessage: returnMessage, - values: returnDataParts + values: returnDataParts, }; } /** * Override this method (in a subclass of {@link ResultsParser}) if the basic heuristics of the parser are not sufficient. */ - protected createBundleWithCustomHeuristics(_transaction: ITransactionOnNetwork, _transactionMetadata: TransactionMetadata): UntypedOutcomeBundle | null { + protected createBundleWithCustomHeuristics( + _transaction: ITransactionOnNetwork, + _transactionMetadata: TransactionMetadata, + ): UntypedOutcomeBundle | null { return null; } - private createBundleWithFallbackHeuristics(transaction: ITransactionOnNetwork, transactionMetadata: TransactionMetadata): UntypedOutcomeBundle | null { + private createBundleWithFallbackHeuristics( + transaction: ITransactionOnNetwork, + transactionMetadata: TransactionMetadata, + ): UntypedOutcomeBundle | null { let contractAddress = new Address(transactionMetadata.receiver); // Search the nested logs for matching events (writeLog): for (const resultItem of transaction.contractResults.items) { - let writeLogWithReturnData = resultItem.logs.findSingleOrNoneEvent(WellKnownEvents.OnWriteLog, event => { + let writeLogWithReturnData = resultItem.logs.findSingleOrNoneEvent(WellKnownEvents.OnWriteLog, (event) => { let addressIsSender = event.address.bech32() == transaction.sender.bech32(); let firstTopicIsContract = event.topics[0]?.hex() == contractAddress.hex(); return addressIsSender && firstTopicIsContract; @@ -334,7 +350,7 @@ export class ResultsParser { return { returnCode: returnCode, returnMessage: returnMessage, - values: returnDataParts + values: returnDataParts, }; } } @@ -342,7 +358,7 @@ export class ResultsParser { return null; } - protected sliceDataFieldInParts(data: string): { returnCode: ReturnCode, returnDataParts: Buffer[] } { + protected sliceDataFieldInParts(data: string): { returnCode: ReturnCode; returnDataParts: Buffer[] } { // By default, skip the first part, which is usually empty (e.g. "[empty]@6f6b") let startingIndex = 1; diff --git a/src/smartcontracts/smartContract.ts b/src/smartcontracts/smartContract.ts index de24dde2..b06b7a5a 100644 --- a/src/smartcontracts/smartContract.ts +++ b/src/smartcontracts/smartContract.ts @@ -10,7 +10,14 @@ import { guardValueIsSet } from "../utils"; import { CodeMetadata } from "./codeMetadata"; import { ContractFunction } from "./function"; import { Interaction } from "./interaction"; -import { CallArguments, DeployArguments, ICodeMetadata, ISmartContract, QueryArguments, UpgradeArguments } from "./interface"; +import { + CallArguments, + DeployArguments, + ICodeMetadata, + ISmartContract, + QueryArguments, + UpgradeArguments, +} from "./interface"; import { NativeSerializer } from "./nativeSerializer"; import { Query } from "./query"; import { EndpointDefinition, TypedValue } from "./typesystem"; @@ -38,8 +45,8 @@ export class SmartContract implements ISmartContract { /** * This object contains a function for each endpoint defined by the contract. * (a bit similar to web3js's "contract.methods"). - * - * This is an alternative to {@link methodsExplicit}. + * + * This is an alternative to {@link methodsExplicit}. * Unlike {@link methodsExplicit}, automatic type inference (wrt. ABI) is applied when using {@link methods}. */ public readonly methods: { [key: string]: (args?: any[]) => Interaction } = {}; @@ -47,7 +54,7 @@ export class SmartContract implements ISmartContract { /** * Create a SmartContract object by providing its address on the Network. */ - constructor(options: { address?: IAddress, abi?: IAbi } = {}) { + constructor(options: { address?: IAddress; abi?: IAbi } = {}) { this.address = options.address || Address.empty(); this.abi = options.abi; @@ -109,8 +116,21 @@ export class SmartContract implements ISmartContract { /** * Creates a {@link Transaction} for deploying the Smart Contract to the Network. */ - deploy({ deployer, code, codeMetadata, initArguments, value, gasLimit, gasPrice, chainID }: DeployArguments): Transaction { - Compatibility.guardAddressIsSetAndNonZero(deployer, "'deployer' of SmartContract.deploy()", "pass the actual address to deploy()"); + deploy({ + deployer, + code, + codeMetadata, + initArguments, + value, + gasLimit, + gasPrice, + chainID, + }: DeployArguments): Transaction { + Compatibility.guardAddressIsSetAndNonZero( + deployer, + "'deployer' of SmartContract.deploy()", + "pass the actual address to deploy()", + ); const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); const factory = new SmartContractTransactionsFactory({ @@ -140,23 +160,22 @@ export class SmartContract implements ISmartContract { } private getMetadataPropertiesAsObject(codeMetadata?: ICodeMetadata): { - upgradeable: boolean, - readable: boolean, - payable: boolean, - payableBySc: boolean + upgradeable: boolean; + readable: boolean; + payable: boolean; + payableBySc: boolean; } { let metadata: CodeMetadata; if (codeMetadata) { metadata = CodeMetadata.fromBytes(Buffer.from(codeMetadata.toString(), "hex")); - } - else { + } else { metadata = new CodeMetadata(); } const metadataAsJson = metadata.toJSON() as { - upgradeable: boolean, - readable: boolean, - payable: boolean, - payableBySc: boolean + upgradeable: boolean; + readable: boolean; + payable: boolean; + payableBySc: boolean; }; return metadataAsJson; @@ -165,8 +184,21 @@ export class SmartContract implements ISmartContract { /** * Creates a {@link Transaction} for upgrading the Smart Contract on the Network. */ - upgrade({ caller, code, codeMetadata, initArguments, value, gasLimit, gasPrice, chainID }: UpgradeArguments): Transaction { - Compatibility.guardAddressIsSetAndNonZero(caller, "'caller' of SmartContract.upgrade()", "pass the actual address to upgrade()"); + upgrade({ + caller, + code, + codeMetadata, + initArguments, + value, + gasLimit, + gasPrice, + chainID, + }: UpgradeArguments): Transaction { + Compatibility.guardAddressIsSetAndNonZero( + caller, + "'caller' of SmartContract.upgrade()", + "pass the actual address to upgrade()", + ); this.ensureHasAddress(); @@ -202,7 +234,11 @@ export class SmartContract implements ISmartContract { * Creates a {@link Transaction} for calling (a function of) the Smart Contract. */ call({ func, args, value, gasLimit, receiver, gasPrice, chainID, caller }: CallArguments): Transaction { - Compatibility.guardAddressIsSetAndNonZero(caller, "'caller' of SmartContract.call()", "pass the actual address to call()"); + Compatibility.guardAddressIsSetAndNonZero( + caller, + "'caller' of SmartContract.call()", + "pass the actual address to call()", + ); this.ensureHasAddress(); @@ -238,7 +274,7 @@ export class SmartContract implements ISmartContract { func: func, args: args, value: value, - caller: caller + caller: caller, }); } @@ -249,9 +285,9 @@ export class SmartContract implements ISmartContract { } /** - * Computes the address of a Smart Contract. + * Computes the address of a Smart Contract. * The address is computed deterministically, from the address of the owner and the nonce of the deployment transaction. - * + * * @param owner The owner of the Smart Contract * @param nonce The owner nonce used for the deployment transaction */ From 29f1c3974f1e859e7b5e5af9c2c0afa823d0e00c Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 17 Apr 2024 15:11:13 +0300 Subject: [PATCH 12/16] tests formatting --- src/smartcontracts/interaction.spec.ts | 191 +++++---- src/smartcontracts/interactionChecker.spec.ts | 79 ++-- src/smartcontracts/nativeSerializer.spec.ts | 384 +++++++++++------- src/smartcontracts/query.spec.ts | 4 +- src/smartcontracts/resultsParser.spec.ts | 190 +++++---- src/smartcontracts/smartContract.spec.ts | 70 +++- .../transactionPayloadBuilders.spec.ts | 15 +- 7 files changed, 564 insertions(+), 369 deletions(-) diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 0d3d7329..4777691c 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -7,7 +7,7 @@ import { loadTestWallets, MockNetworkProvider, setupUnitTestWatcherTimeouts, - TestWallet + TestWallet, } from "../testutils"; import { ContractController } from "../testutils/contractController"; import { Token, TokenTransfer } from "../tokens"; @@ -53,13 +53,14 @@ describe("test smart contract interactor", function () { const TokenFoo = (amount: BigNumber.Value) => TokenTransfer.fungibleFromAmount("FOO-6ce17b", amount, 0); const TokenBar = (amount: BigNumber.Value) => TokenTransfer.fungibleFromAmount("BAR-5bc08f", amount, 3); - const LKMEX = (nonce: number, amount: BigNumber.Value) => TokenTransfer.metaEsdtFromAmount("LKMEX-aab910", nonce, amount, 18); - const Strămoși = (nonce: number) => TokenTransfer.nonFungible("MOS-b9b4b2", nonce); + const LKMEX = (nonce: number, amount: BigNumber.Value) => + TokenTransfer.metaEsdtFromAmount("LKMEX-aab910", nonce, amount, 18); + const nonFungibleToken = (nonce: number) => TokenTransfer.nonFungible("MOS-b9b4b2", nonce); const hexFoo = "464f4f2d366365313762"; const hexBar = "4241522d356263303866"; const hexLKMEX = "4c4b4d45582d616162393130"; - const hexStrămoși = "4d4f532d623962346232"; + const hexNFT = "4d4f532d623962346232"; const hexContractAddress = new Address(contract.getAddress().bech32()).hex(); const hexDummyFunction = "64756d6d79"; @@ -79,7 +80,10 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`, + ); // Meta ESDT (special SFT), single, but using "withSender()" (recommended) transaction = new Interaction(contract, dummyFunction, []) @@ -89,27 +93,36 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`, + ); // NFT, single transaction = new Interaction(contract, dummyFunction, []) .withSender(alice) - .withSingleESDTNFTTransfer(Strămoși(1)) + .withSingleESDTNFTTransfer(nonFungibleToken(1)) .buildTransaction(); assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexStrămoși}@01@01@${hexContractAddress}@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `ESDTNFTTransfer@${hexNFT}@01@01@${hexContractAddress}@${hexDummyFunction}`, + ); // NFT, single, but using "withSender()" (recommended) transaction = new Interaction(contract, dummyFunction, []) - .withSingleESDTNFTTransfer(Strămoși(1)) + .withSingleESDTNFTTransfer(nonFungibleToken(1)) .withSender(alice) .buildTransaction(); assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexStrămoși}@01@01@${hexContractAddress}@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `ESDTNFTTransfer@${hexNFT}@01@01@${hexContractAddress}@${hexDummyFunction}`, + ); // ESDT, multiple transaction = new Interaction(contract, dummyFunction, []) @@ -119,7 +132,10 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`, + ); // ESDT, multiple, but using "withSender()" (recommended) transaction = new Interaction(contract, dummyFunction, []) @@ -129,21 +145,27 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexFoo}@@03@${hexBar}@@0c44@${hexDummyFunction}`, + ); // NFT, multiple transaction = new Interaction(contract, dummyFunction, []) .withSender(alice) - .withMultiESDTNFTTransfer([Strămoși(1), Strămoși(42)]) + .withMultiESDTNFTTransfer([nonFungibleToken(1), nonFungibleToken(42)]) .buildTransaction(); assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexStrămoși}@01@01@${hexStrămoși}@2a@01@${hexDummyFunction}`); + assert.equal( + transaction.getData().toString(), + `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexNFT}@01@01@${hexNFT}@2a@01@${hexDummyFunction}`, + ); // NFT, multiple, but using "withSender()" (recommended) transaction = new Interaction(contract, dummyFunction, []) - .withMultiESDTNFTTransfer([Strămoși(1), Strămoși(42)]) + .withMultiESDTNFTTransfer([nonFungibleToken(1), nonFungibleToken(42)]) .withSender(alice) .buildTransaction(); @@ -188,9 +210,7 @@ describe("test smart contract interactor", function () { let contract = new SmartContract({ address: dummyAddress, abi: abiRegistry }); let controller = new ContractController(provider); - let interaction = ( - contract.methods.getUltimateAnswer().withGasLimit(543210).withChainID("T") - ); + let interaction = contract.methods.getUltimateAnswer().withGasLimit(543210).withChainID("T"); assert.equal(contract.getAddress(), dummyAddress); assert.deepEqual(interaction.getFunction(), new ContractFunction("getUltimateAnswer")); @@ -199,13 +219,15 @@ describe("test smart contract interactor", function () { provider.mockQueryContractOnFunction( "getUltimateAnswer", - new ContractQueryResponse({ returnData: [Buffer.from([42]).toString("base64")], returnCode: "ok" }) + new ContractQueryResponse({ returnData: [Buffer.from([42]).toString("base64")], returnCode: "ok" }), ); // Query - let { values: queryValues, firstValue: queryAnwser, returnCode: queryCode } = await controller.query( - interaction - ); + let { + values: queryValues, + firstValue: queryAnwser, + returnCode: queryCode, + } = await controller.query(interaction); assert.lengthOf(queryValues, 1); assert.deepEqual(queryAnwser!.valueOf(), new BigNumber(42)); assert.isTrue(queryCode.equals(ReturnCode.Ok)); @@ -219,7 +241,7 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getData().toString(), "getUltimateAnswer"); assert.equal( transaction.getHash().toString(), - "3579ad09099feb9755c860ddd225251170806d833342e912fccdfe2ed5c3a364" + "3579ad09099feb9755c860ddd225251170806d833342e912fccdfe2ed5c3a364", ); transaction = interaction.withNonce(1).buildTransaction(); @@ -229,7 +251,7 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getNonce().valueOf(), 1); assert.equal( transaction.getHash().toString(), - "ad513ce7c5d371d30e48f073326899766736eac1ac231d847d45bc3facbcb496" + "ad513ce7c5d371d30e48f073326899766736eac1ac231d847d45bc3facbcb496", ); // Execute, and wait for execution @@ -258,7 +280,7 @@ describe("test smart contract interactor", function () { // For "get()", return fake 7 provider.mockQueryContractOnFunction( "get", - new ContractQueryResponse({ returnData: [Buffer.from([7]).toString("base64")], returnCode: "ok" }) + new ContractQueryResponse({ returnData: [Buffer.from([7]).toString("base64")], returnCode: "ok" }), ); // Query "get()" @@ -267,23 +289,25 @@ describe("test smart contract interactor", function () { assert.deepEqual(counterValue!.valueOf(), new BigNumber(7)); let incrementTransaction = incrementInteraction - .withSender(alice.address) - .withNonce(14) - .withChainID("mock") - .buildTransaction(); + .withSender(alice.address) + .withNonce(14) + .withChainID("mock") + .buildTransaction(); incrementTransaction.applySignature(await alice.signer.sign(incrementTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@08"); - let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction); + 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 let decrementTransaction = decrementInteraction - .withSender(alice.address) - .withNonce(15) - .withChainID("mock") - .buildTransaction(); + .withSender(alice.address) + .withNonce(15) + .withChainID("mock") + .buildTransaction(); decrementTransaction.applySignature(await alice.signer.sign(decrementTransaction.serializeForSigning())); await provider.sendTransaction(decrementTransaction); @@ -296,7 +320,9 @@ describe("test smart contract interactor", function () { decrementTransaction = decrementInteraction.withNonce(17).buildTransaction(); decrementTransaction.applySignature(await alice.signer.sign(decrementTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@05"); - let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); + let { + bundle: { firstValue: valueAfterDecrement }, + } = await controller.execute(decrementInteraction, decrementTransaction); assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(5)); }); @@ -307,56 +333,57 @@ describe("test smart contract interactor", function () { let contract = new SmartContract({ address: dummyAddress, abi: abiRegistry }); let controller = new ContractController(provider); - let startInteraction = ( - contract.methodsExplicit - .start([ - BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue("lucky-token"), - new BigUIntValue(1), - OptionValue.newMissing(), - OptionValue.newMissing(), - OptionValue.newProvided(new U32Value(1)), - OptionValue.newMissing(), - OptionValue.newMissing(), - OptionalValue.newMissing() - ]) - .withGasLimit(5000000) - .check() - ); - - let statusInteraction = ( - contract.methods.status(["lucky"]).withGasLimit(5000000) - ); - - let getLotteryInfoInteraction = ( - contract.methods.getLotteryInfo(["lucky"]).withGasLimit(5000000) - ); + let startInteraction = contract.methodsExplicit + .start([ + BytesValue.fromUTF8("lucky"), + new TokenIdentifierValue("lucky-token"), + new BigUIntValue(1), + OptionValue.newMissing(), + OptionValue.newMissing(), + OptionValue.newProvided(new U32Value(1)), + OptionValue.newMissing(), + OptionValue.newMissing(), + OptionalValue.newMissing(), + ]) + .withGasLimit(5000000) + .check(); + + let statusInteraction = contract.methods.status(["lucky"]).withGasLimit(5000000); + + let getLotteryInfoInteraction = contract.methods.getLotteryInfo(["lucky"]).withGasLimit(5000000); // start() let startTransaction = startInteraction - .withSender(alice.address) - .withNonce(14) - .withChainID("mock") - .buildTransaction(); + .withSender(alice.address) + .withNonce(14) + .withChainID("mock") + .buildTransaction(); startTransaction.applySignature(await alice.signer.sign(startTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b"); - let { bundle: { returnCode: startReturnCode, values: startReturnValues } } = await controller.execute(startInteraction, startTransaction); + let { + bundle: { returnCode: startReturnCode, values: startReturnValues }, + } = await controller.execute(startInteraction, startTransaction); - assert.equal(startTransaction.getData().toString(), "start@6c75636b79@6c75636b792d746f6b656e@01@@@0100000001@@"); + assert.equal( + startTransaction.getData().toString(), + "start@6c75636b79@6c75636b792d746f6b656e@01@@@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 - .withSender(alice.address) - .withNonce(15) - .withChainID("mock") - .buildTransaction(); + .withSender(alice.address) + .withNonce(15) + .withChainID("mock") + .buildTransaction(); statusTransaction.applySignature(await alice.signer.sign(statusTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@01"); - let { bundle: { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } } = await controller.execute(statusInteraction, statusTransaction); + 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)); @@ -365,14 +392,20 @@ describe("test smart contract interactor", function () { // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) let getLotteryInfoTransaction = getLotteryInfoInteraction - .withSender(alice.address) - .withNonce(15) - .withChainID("mock") - .buildTransaction(); - - getLotteryInfoTransaction.applySignature(await alice.signer.sign(getLotteryInfoTransaction.serializeForSigning())); - provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000"); - let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue } } = await controller.execute(getLotteryInfoInteraction, getLotteryInfoTransaction); + .withSender(alice.address) + .withNonce(15) + .withChainID("mock") + .buildTransaction(); + + getLotteryInfoTransaction.applySignature( + await alice.signer.sign(getLotteryInfoTransaction.serializeForSigning()), + ); + provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult( + "@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000", + ); + let { + bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue }, + } = await controller.execute(getLotteryInfoInteraction, getLotteryInfoTransaction); assert.equal(getLotteryInfoTransaction.getData().toString(), "getLotteryInfo@6c75636b79"); assert.isTrue(infoReturnCode.equals(ReturnCode.Ok)); @@ -385,7 +418,7 @@ describe("test smart contract interactor", function () { deadline: new BigNumber("0x000000005fc2b9db", 16), max_entries_per_user: new BigNumber(0xffffffff), prize_distribution: Buffer.from([0x64]), - prize_pool: new BigNumber("94720000000000000000000") + prize_pool: new BigNumber("94720000000000000000000"), }); }); }); diff --git a/src/smartcontracts/interactionChecker.spec.ts b/src/smartcontracts/interactionChecker.spec.ts index 00f51117..84da7dab 100644 --- a/src/smartcontracts/interactionChecker.spec.ts +++ b/src/smartcontracts/interactionChecker.spec.ts @@ -6,7 +6,15 @@ import { TokenTransfer } from "../tokens"; import { Interaction } from "./interaction"; import { InteractionChecker } from "./interactionChecker"; import { SmartContract } from "./smartContract"; -import { BigUIntType, BigUIntValue, OptionalType, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; +import { + BigUIntType, + BigUIntValue, + OptionalType, + OptionalValue, + OptionValue, + TokenIdentifierValue, + U32Value, +} from "./typesystem"; import { BytesValue } from "./typesystem/bytes"; describe("integration tests: test checker within interactor", function () { @@ -19,15 +27,25 @@ describe("integration tests: test checker within interactor", function () { let endpoint = abiRegistry.getEndpoint("getUltimateAnswer"); // Send value to non-payable - assert.throw(() => { - let interaction = (contract.methods.getUltimateAnswer()).withValue(TokenTransfer.egldFromAmount(1)); - checker.checkInteraction(interaction, endpoint); - }, errors.ErrContractInteraction, "cannot send EGLD value to non-payable"); + assert.throw( + () => { + let interaction = (contract.methods.getUltimateAnswer()).withValue( + TokenTransfer.egldFromAmount(1), + ); + checker.checkInteraction(interaction, endpoint); + }, + errors.ErrContractInteraction, + "cannot send EGLD value to non-payable", + ); // Bad arguments - assert.throw(() => { - contract.methods.getUltimateAnswer(["abba"]); - }, Error, "Wrong number of arguments for endpoint getUltimateAnswer: expected between 0 and 0 arguments, have 1"); + assert.throw( + () => { + contract.methods.getUltimateAnswer(["abba"]); + }, + Error, + "Wrong number of arguments for endpoint getUltimateAnswer: expected between 0 and 0 arguments, have 1", + ); }); it("should detect errors for 'lottery'", async function () { @@ -36,27 +54,32 @@ describe("integration tests: test checker within interactor", function () { let endpoint = abiRegistry.getEndpoint("start"); // Bad number of arguments - assert.throw(() => { - contract.methods.start([ - "lucky", - TokenTransfer.egldFromAmount(1) - ]); - }, Error, "Wrong number of arguments for endpoint start: expected between 8 and 9 arguments, have 2"); + assert.throw( + () => { + contract.methods.start(["lucky", TokenTransfer.egldFromAmount(1)]); + }, + Error, + "Wrong number of arguments for endpoint start: expected between 8 and 9 arguments, have 2", + ); // Bad types (U64 instead of U32) - assert.throw(() => { - let interaction = contract.methodsExplicit.start([ - BytesValue.fromUTF8("lucky"), - new TokenIdentifierValue("lucky-token"), - new BigUIntValue(1), - OptionValue.newMissing(), - 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"); + assert.throw( + () => { + let interaction = contract.methodsExplicit.start([ + BytesValue.fromUTF8("lucky"), + new TokenIdentifierValue("lucky-token"), + new BigUIntValue(1), + OptionValue.newMissing(), + 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", + ); }); }); diff --git a/src/smartcontracts/nativeSerializer.spec.ts b/src/smartcontracts/nativeSerializer.spec.ts index 2a8cb233..257c9267 100644 --- a/src/smartcontracts/nativeSerializer.spec.ts +++ b/src/smartcontracts/nativeSerializer.spec.ts @@ -3,7 +3,35 @@ import { assert } from "chai"; import { Address } from "../address"; import { ErrInvalidArgument } from "../errors"; import { NativeSerializer } from "./nativeSerializer"; -import { AbiRegistry, AddressType, AddressValue, BigUIntType, BooleanType, BooleanValue, CompositeType, CompositeValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, ListType, NullType, OptionalType, OptionalValue, OptionType, OptionValue, TupleType, TypePlaceholder, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, VariadicType, VariadicValue } from "./typesystem"; +import { + AbiRegistry, + AddressType, + AddressValue, + BigUIntType, + BooleanType, + BooleanValue, + CompositeType, + CompositeValue, + EndpointDefinition, + EndpointModifiers, + EndpointParameterDefinition, + ListType, + NullType, + OptionalType, + OptionalValue, + OptionType, + OptionValue, + TupleType, + TypePlaceholder, + U32Type, + U32Value, + U64Type, + U64Value, + U8Type, + U8Value, + VariadicType, + VariadicValue, +} from "./typesystem"; import { BytesType, BytesValue } from "./typesystem/bytes"; describe("test native serializer", () => { @@ -16,12 +44,15 @@ describe("test native serializer", () => { new EndpointParameterDefinition("", "", new BytesType()), new EndpointParameterDefinition("", "", new OptionType(new U32Type())), new EndpointParameterDefinition("", "", new OptionType(new U32Type())), - new EndpointParameterDefinition("", "", new OptionalType(new BytesType())) + new EndpointParameterDefinition("", "", new OptionalType(new BytesType())), ]; const endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers); const p0 = 42; - const p1 = [new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"), new Address("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede")]; + const p1 = [ + new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"), + new Address("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede"), + ]; const p2 = Buffer.from("abba", "hex"); const p3 = Number(0xabba); const p4 = null; @@ -60,18 +91,28 @@ describe("test native serializer", () => { const endpointModifiers = new EndpointModifiers("", []); const inputParameters = [ new EndpointParameterDefinition("", "", new VariadicType(new U32Type(), true)), - new EndpointParameterDefinition("", "", new VariadicType(new BytesType(), true)) + new EndpointParameterDefinition("", "", new VariadicType(new BytesType(), true)), ]; const endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers); // Implicit counted-variadic (not supported). - assert.throws(() => NativeSerializer.nativeToTypedValues([8, 9, 10, "a", "b", "c"], endpoint), ErrInvalidArgument); + assert.throws( + () => NativeSerializer.nativeToTypedValues([8, 9, 10, "a", "b", "c"], endpoint), + ErrInvalidArgument, + ); // Explicit, non-empty counted-variadic. - let typedValues = NativeSerializer.nativeToTypedValues([ - VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)), - VariadicValue.fromItemsCounted(BytesValue.fromUTF8("a"), BytesValue.fromUTF8("b"), BytesValue.fromUTF8("c")) - ], endpoint); + let typedValues = NativeSerializer.nativeToTypedValues( + [ + VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)), + VariadicValue.fromItemsCounted( + BytesValue.fromUTF8("a"), + BytesValue.fromUTF8("b"), + BytesValue.fromUTF8("c"), + ), + ], + endpoint, + ); assert.lengthOf(typedValues, 2); assert.deepEqual(typedValues[0].getType(), new VariadicType(new U32Type(), true)); @@ -80,10 +121,10 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[1].valueOf(), [Buffer.from("a"), Buffer.from("b"), Buffer.from("c")]); // Explicit, empty counted-variadic. - typedValues = NativeSerializer.nativeToTypedValues([ - VariadicValue.fromItemsCounted(), - VariadicValue.fromItemsCounted() - ], endpoint); + typedValues = NativeSerializer.nativeToTypedValues( + [VariadicValue.fromItemsCounted(), VariadicValue.fromItemsCounted()], + endpoint, + ); assert.lengthOf(typedValues, 2); assert.deepEqual(typedValues[0].getType(), new VariadicType(new TypePlaceholder(), true)); @@ -96,7 +137,7 @@ describe("test native serializer", () => { const endpointModifiers = new EndpointModifiers("", []); const inputParameters = [ new EndpointParameterDefinition("", "", new VariadicType(new U32Type(), true)), - new EndpointParameterDefinition("", "", new VariadicType(new BytesType(), false)) + new EndpointParameterDefinition("", "", new VariadicType(new BytesType(), false)), ]; const endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers); @@ -104,9 +145,10 @@ describe("test native serializer", () => { assert.throws(() => NativeSerializer.nativeToTypedValues([8, 9, 10], endpoint), ErrInvalidArgument); // Explicit counted-variadic, empty implicit regular variadic. - let typedValues = NativeSerializer.nativeToTypedValues([ - VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)) - ], endpoint); + let typedValues = NativeSerializer.nativeToTypedValues( + [VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10))], + endpoint, + ); assert.lengthOf(typedValues, 2); assert.deepEqual(typedValues[0].getType(), new VariadicType(new U32Type(), true)); @@ -115,10 +157,10 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[1].valueOf(), []); // Explicit counted-variadic, non-empty implicit regular variadic. - typedValues = NativeSerializer.nativeToTypedValues([ - VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)), - "a", "b", "c" - ], endpoint); + typedValues = NativeSerializer.nativeToTypedValues( + [VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)), "a", "b", "c"], + endpoint, + ); assert.lengthOf(typedValues, 2); assert.deepEqual(typedValues[0].getType(), new VariadicType(new U32Type(), true)); @@ -129,20 +171,23 @@ describe("test native serializer", () => { it("should should handle optionals in a strict manner (but it does not)", async () => { const endpoint = AbiRegistry.create({ - "endpoints": [ + endpoints: [ { - "name": "foo", - "inputs": [{ - "type": "optional" - }], - "outputs": [] - } - ] + name: "foo", + inputs: [ + { + type: "optional", + }, + ], + outputs: [], + }, + ], }).getEndpoint("foo"); - let typedValues = NativeSerializer.nativeToTypedValues([ - new OptionalValue(new BooleanType(), new BooleanValue(true)) - ], endpoint); + let typedValues = NativeSerializer.nativeToTypedValues( + [new OptionalValue(new BooleanType(), new BooleanValue(true))], + endpoint, + ); // Isn't this a bug? Shouldn't it be be OptionalType(BooleanType()), instead? assert.deepEqual(typedValues[0].getType(), new BooleanType()); @@ -156,7 +201,7 @@ describe("test native serializer", () => { let inputParameters = [ new EndpointParameterDefinition("a", "a", new BigUIntType()), new EndpointParameterDefinition("b", "b", new ListType(new AddressType())), - new EndpointParameterDefinition("c", "c", new BytesType()) + new EndpointParameterDefinition("c", "c", new BytesType()), ]; let endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers); @@ -176,29 +221,27 @@ describe("test native serializer", () => { it("should accept a mix between typed values and regular JavaScript objects (variadic, optionals)", async () => { const endpoint = AbiRegistry.create({ - "endpoints": [ + endpoints: [ { - "name": "foo", - "inputs": [{ - "type": "bool" - }, { - "type": "optional" - }, { - "type": "variadic" - }], - "outputs": [] - } - ] + name: "foo", + inputs: [ + { + type: "bool", + }, + { + type: "optional", + }, + { + type: "variadic", + }, + ], + outputs: [], + }, + ], }).getEndpoint("foo"); // Using only native JavaScript objects - let typedValues = NativeSerializer.nativeToTypedValues([ - true, - null, - true, - false, - true - ], endpoint); + let typedValues = NativeSerializer.nativeToTypedValues([true, null, true, false, true], endpoint); assert.deepEqual(typedValues[0].getType(), new BooleanType()); assert.deepEqual(typedValues[0].valueOf(), true); @@ -208,11 +251,14 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[2].valueOf(), [true, false, true]); // Using both native JavaScript objects and typed values - typedValues = NativeSerializer.nativeToTypedValues([ - true, - null, - VariadicValue.fromItems(new BooleanValue(true), new BooleanValue(false), new BooleanValue(true)), - ], endpoint); + typedValues = NativeSerializer.nativeToTypedValues( + [ + true, + null, + VariadicValue.fromItems(new BooleanValue(true), new BooleanValue(false), new BooleanValue(true)), + ], + endpoint, + ); assert.deepEqual(typedValues[0].getType(), new BooleanType()); assert.deepEqual(typedValues[0].valueOf(), true); @@ -224,15 +270,17 @@ describe("test native serializer", () => { it("should accept a mix between typed values and regular JavaScript objects (composite, optionals)", async () => { const endpoint = AbiRegistry.create({ - "endpoints": [ + endpoints: [ { - "name": "foo", - "inputs": [{ - "type": "optional>", - }], - "outputs": [] - } - ] + name: "foo", + inputs: [ + { + type: "optional>", + }, + ], + outputs: [], + }, + ], }).getEndpoint("foo"); const compositeType = new CompositeType(new AddressType(), new U64Type()); @@ -256,25 +304,24 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[0].valueOf(), [address, new BigNumber(42)]); // Pass only typed values - typedValues = NativeSerializer.nativeToTypedValues([new OptionalValue(optionalCompositeType, compositeValue)], endpoint); + typedValues = NativeSerializer.nativeToTypedValues( + [new OptionalValue(optionalCompositeType, compositeValue)], + endpoint, + ); assert.deepEqual(typedValues[0].getType(), optionalCompositeType); assert.deepEqual(typedValues[0], optionalCompositeValue); assert.deepEqual(typedValues[0].valueOf(), [address, new BigNumber(42)]); // Pass a mix of native and typed values - typedValues = NativeSerializer.nativeToTypedValues([ - [new AddressValue(address), 42] - ], endpoint); + typedValues = NativeSerializer.nativeToTypedValues([[new AddressValue(address), 42]], endpoint); assert.deepEqual(typedValues[0].getType(), optionalCompositeType); assert.deepEqual(typedValues[0], optionalCompositeValue); assert.deepEqual(typedValues[0].valueOf(), [address, new BigNumber(42)]); // Pass a mix of native and typed values - typedValues = NativeSerializer.nativeToTypedValues([ - [addressBech32, new U64Value(42)], - ], endpoint); + typedValues = NativeSerializer.nativeToTypedValues([[addressBech32, new U64Value(42)]], endpoint); assert.deepEqual(typedValues[0].getType(), optionalCompositeType); assert.deepEqual(typedValues[0], optionalCompositeValue); @@ -283,79 +330,99 @@ describe("test native serializer", () => { it("should accept a mix between typed values and regular JavaScript objects (tuples)", async () => { const endpoint = AbiRegistry.create({ - "endpoints": [ + endpoints: [ { - "name": "foo", - "inputs": [{ - "type": "tuple", - }, { - "type": "tuple>", - }, { - "type": "List>", - }, { - "type": "u64" - }], - "outputs": [] - } - ] + name: "foo", + inputs: [ + { + type: "tuple", + }, + { + type: "tuple>", + }, + { + type: "List>", + }, + { + type: "u64", + }, + ], + outputs: [], + }, + ], }).getEndpoint("foo"); // Pass only native values - let typedValues = NativeSerializer.nativeToTypedValues([ - [42, true], - [43, false], - [[44, false], [45, true]], - 46 - ], endpoint); + let typedValues = NativeSerializer.nativeToTypedValues( + [ + [42, true], + [43, false], + [ + [44, false], + [45, true], + ], + 46, + ], + endpoint, + ); assert.deepEqual(typedValues[0].getType(), new TupleType(new U64Type(), new BooleanType())); assert.deepEqual(typedValues[0].valueOf(), { field0: new BigNumber(42), field1: true }); assert.deepEqual(typedValues[1].getType(), new TupleType(new U8Type(), new OptionType(new BooleanType()))); assert.deepEqual(typedValues[1].valueOf(), { field0: new BigNumber(43), field1: false }); assert.deepEqual(typedValues[2].getType(), new ListType(new TupleType(new U8Type(), new BooleanType()))); - assert.deepEqual(typedValues[2].valueOf(), [{ field0: new BigNumber(44), field1: false }, { field0: new BigNumber(45), field1: true }]); + assert.deepEqual(typedValues[2].valueOf(), [ + { field0: new BigNumber(44), field1: false }, + { field0: new BigNumber(45), field1: true }, + ]); // Pass a mix of native and typed values - typedValues = NativeSerializer.nativeToTypedValues([ - [new U64Value(42), true], - [43, OptionValue.newProvided(new BooleanValue(false))], - [[new U8Value(44), false], [45, new BooleanValue(true)]], - 46 - ], endpoint); + typedValues = NativeSerializer.nativeToTypedValues( + [ + [new U64Value(42), true], + [43, OptionValue.newProvided(new BooleanValue(false))], + [ + [new U8Value(44), false], + [45, new BooleanValue(true)], + ], + 46, + ], + endpoint, + ); assert.deepEqual(typedValues[0].getType(), new TupleType(new U64Type(), new BooleanType())); assert.deepEqual(typedValues[0].valueOf(), { field0: new BigNumber(42), field1: true }); assert.deepEqual(typedValues[1].getType(), new TupleType(new U8Type(), new OptionType(new BooleanType()))); assert.deepEqual(typedValues[1].valueOf(), { field0: new BigNumber(43), field1: false }); assert.deepEqual(typedValues[2].getType(), new ListType(new TupleType(new U8Type(), new BooleanType()))); - assert.deepEqual(typedValues[2].valueOf(), [{ field0: new BigNumber(44), field1: false }, { field0: new BigNumber(45), field1: true }]); + assert.deepEqual(typedValues[2].valueOf(), [ + { field0: new BigNumber(44), field1: false }, + { field0: new BigNumber(45), field1: true }, + ]); }); - it('should accept no value for variadic types', async () => { + it("should accept no value for variadic types", async () => { const endpoint = AbiRegistry.create({ endpoints: [ { - name: 'foo', + name: "foo", inputs: [ { - type: 'u64', + type: "u64", }, { - name: 'features', - type: 'variadic', + name: "features", + type: "variadic", multi_arg: true, }, ], outputs: [], }, ], - }).getEndpoint('foo'); + }).getEndpoint("foo"); // Using both native JavaScript objects and typed values - const typedValues = NativeSerializer.nativeToTypedValues( - [42], - endpoint - ); + const typedValues = NativeSerializer.nativeToTypedValues([42], endpoint); assert.deepEqual(typedValues[0].getType(), new U64Type()); assert.deepEqual(typedValues[0].valueOf(), new BigNumber(42)); @@ -365,56 +432,61 @@ describe("test native serializer", () => { it("should perform type inference (enums)", async () => { const abiRegistry = AbiRegistry.create({ - "endpoints": [ + endpoints: [ { - "name": "foo", - "inputs": [{ - "type": "MyEnum", - }, { - "type": "MyEnum", - }, { - "type": "MyEnum", - }, { - "type": "MyEnum", - }], - "outputs": [] - } + name: "foo", + inputs: [ + { + type: "MyEnum", + }, + { + type: "MyEnum", + }, + { + type: "MyEnum", + }, + { + type: "MyEnum", + }, + ], + outputs: [], + }, ], - "types": { - "MyEnum": { - "type": "enum", - "variants": [ + types: { + MyEnum: { + type: "enum", + variants: [ { - "name": "Nothing", - "discriminant": 0 + name: "Nothing", + discriminant: 0, }, { - "name": "Something", - "discriminant": 1, - "fields": [ + name: "Something", + discriminant: 1, + fields: [ { - "name": "0", - "type": "Address" - } - ] + name: "0", + type: "Address", + }, + ], }, { - "name": "Else", - "discriminant": 2, - "fields": [ + name: "Else", + discriminant: 2, + fields: [ { - "name": "x", - "type": "u64" + name: "x", + type: "u64", }, { - "name": "y", - "type": "u64" - } - ] - } - ] + name: "y", + type: "u64", + }, + ], + }, + ], }, - } + }, }); const endpoint = abiRegistry.getEndpoint("foo"); @@ -423,11 +495,14 @@ describe("test native serializer", () => { // Simple enum by discriminant const p0 = 0; // Simple enum by name - const p1 = 'Nothing'; + const p1 = "Nothing"; // Enum with a single field - const p2 = { name: 'Something', fields: { 0: 'erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha' } }; + const p2 = { + name: "Something", + fields: { 0: "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha" }, + }; // Enum with multiple fields - const p3 = { name: 'Else', fields: { x: 42, y: 43 } }; + const p3 = { name: "Else", fields: { x: 42, y: 43 } }; const typedValues = NativeSerializer.nativeToTypedValues([p0, p1, p2, p3], endpoint); @@ -436,8 +511,11 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[1].getType(), enumType); assert.deepEqual(typedValues[1].valueOf(), { name: "Nothing", fields: [] }); assert.deepEqual(typedValues[2].getType(), enumType); - assert.deepEqual(typedValues[2].valueOf(), { name: 'Something', fields: [new Address('erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha')] }); + assert.deepEqual(typedValues[2].valueOf(), { + name: "Something", + fields: [new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha")], + }); assert.deepEqual(typedValues[3].getType(), enumType); - assert.deepEqual(typedValues[3].valueOf(), { name: 'Else', fields: [new BigNumber(42), new BigNumber(43)] }); + assert.deepEqual(typedValues[3].valueOf(), { name: "Else", fields: [new BigNumber(42), new BigNumber(43)] }); }); }); diff --git a/src/smartcontracts/query.spec.ts b/src/smartcontracts/query.spec.ts index 7c953992..090166b1 100644 --- a/src/smartcontracts/query.spec.ts +++ b/src/smartcontracts/query.spec.ts @@ -15,8 +15,8 @@ describe("test smart contract queries", () => { new U32Value(100), BytesValue.fromUTF8("!"), BytesValue.fromHex("abba"), - new BigUIntValue(new BigNumber("1000000000000000000000000000000000")) - ] + new BigUIntValue(new BigNumber("1000000000000000000000000000000000")), + ], }); let args = query.getEncodedArguments(); diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts index 2c033a65..30757b90 100644 --- a/src/smartcontracts/resultsParser.spec.ts +++ b/src/smartcontracts/resultsParser.spec.ts @@ -1,4 +1,12 @@ -import { ContractQueryResponse, ContractResultItem, ContractResults, TransactionEvent, TransactionEventTopic, TransactionLogs, TransactionOnNetwork } from "@multiversx/sdk-network-providers"; +import { + ContractQueryResponse, + ContractResultItem, + ContractResults, + TransactionEvent, + TransactionEventTopic, + TransactionLogs, + TransactionOnNetwork, +} from "@multiversx/sdk-network-providers"; import { TransactionEventData } from "@multiversx/sdk-network-providers/out/transactionEvents"; import BigNumber from "bignumber.js"; import { assert } from "chai"; @@ -12,7 +20,23 @@ import { loadAbiRegistry } from "../testutils"; import { ArgSerializer } from "./argSerializer"; import { ResultsParser } from "./resultsParser"; import { ReturnCode } from "./returnCode"; -import { AbiRegistry, BigUIntType, BigUIntValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, StringType, StringValue, TypedValue, U32Type, U32Value, U64Type, U64Value, VariadicType, VariadicValue } from "./typesystem"; +import { + AbiRegistry, + BigUIntType, + BigUIntValue, + EndpointDefinition, + EndpointModifiers, + EndpointParameterDefinition, + StringType, + StringValue, + TypedValue, + U32Type, + U32Value, + U64Type, + U64Value, + VariadicType, + VariadicValue, +} from "./typesystem"; import { BytesType, BytesValue } from "./typesystem/bytes"; const KnownReturnCodes: string[] = [ @@ -25,13 +49,14 @@ const KnownReturnCodes: string[] = [ ReturnCode.OutOfGas.valueOf(), ReturnCode.AccountCollision.valueOf(), ReturnCode.OutOfFunds.valueOf(), - ReturnCode.CallStackOverFlow.valueOf(), ReturnCode.ContractInvalid.valueOf(), + ReturnCode.CallStackOverFlow.valueOf(), + ReturnCode.ContractInvalid.valueOf(), ReturnCode.ExecutionFailed.valueOf(), // Returned by protocol, not by VM: "insufficient funds", "operation in account not permitted not the owner of the account", "sending value to non payable contract", - "invalid receiver address" + "invalid receiver address", ]; describe("test smart contract results parser", () => { @@ -44,9 +69,9 @@ describe("test smart contract results parser", () => { return [new U64Value(42)]; }, stringToBuffers(_joinedString) { - return [] - } - } + return []; + }, + }, }); const endpoint = new EndpointDefinition("", [], [], new EndpointModifiers("", [])); @@ -63,10 +88,10 @@ describe("test smart contract results parser", () => { return new U64Value(42); }, encodeTopLevel(_typedValue): Buffer { - return Buffer.from([]) + return Buffer.from([]); }, - } - }) + }, + }), }); const outputParameters = [new EndpointParameterDefinition("", "", new U64Type())]; @@ -80,17 +105,14 @@ describe("test smart contract results parser", () => { let endpointModifiers = new EndpointModifiers("", []); let outputParameters = [ new EndpointParameterDefinition("a", "a", new BigUIntType()), - new EndpointParameterDefinition("b", "b", new BytesType()) + new EndpointParameterDefinition("b", "b", new BytesType()), ]; let endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers); let queryResponse = new ContractQueryResponse({ - returnData: [ - Buffer.from([42]).toString("base64"), - Buffer.from("abba", "hex").toString("base64"), - ], + returnData: [Buffer.from([42]).toString("base64"), Buffer.from("abba", "hex").toString("base64")], returnCode: "ok", - returnMessage: "foobar" + returnMessage: "foobar", }); let bundle = parser.parseQueryResponse(queryResponse, endpoint); @@ -106,10 +128,7 @@ describe("test smart contract results parser", () => { const outputParameters = [new EndpointParameterDefinition("a", "a", new VariadicType(new U32Type(), false))]; const endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers); const queryResponse = new ContractQueryResponse({ - returnData: [ - Buffer.from([42]).toString("base64"), - Buffer.from([43]).toString("base64"), - ] + returnData: [Buffer.from([42]).toString("base64"), Buffer.from([43]).toString("base64")], }); const bundle = parser.parseQueryResponse(queryResponse, endpoint); @@ -125,7 +144,7 @@ describe("test smart contract results parser", () => { Buffer.from([2]).toString("base64"), Buffer.from([42]).toString("base64"), Buffer.from([43]).toString("base64"), - ] + ], }); const bundle = parser.parseQueryResponse(queryResponse, endpoint); @@ -138,7 +157,6 @@ describe("test smart contract results parser", () => { new EndpointParameterDefinition("a", "a", new VariadicType(new U32Type(), true)), new EndpointParameterDefinition("b", "b", new VariadicType(new StringType(), true)), new EndpointParameterDefinition("c", "c", new BigUIntType()), - ]; const endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers); const queryResponse = new ContractQueryResponse({ @@ -150,13 +168,16 @@ describe("test smart contract results parser", () => { Buffer.from("a").toString("base64"), Buffer.from("b").toString("base64"), Buffer.from("c").toString("base64"), - Buffer.from([42]).toString("base64") - ] + Buffer.from([42]).toString("base64"), + ], }); const bundle = parser.parseQueryResponse(queryResponse, endpoint); assert.deepEqual(bundle.values[0], VariadicValue.fromItemsCounted(new U32Value(42), new U32Value(43))); - assert.deepEqual(bundle.values[1], VariadicValue.fromItemsCounted(new StringValue("a"), new StringValue("b"), new StringValue("c"))); + assert.deepEqual( + bundle.values[1], + VariadicValue.fromItemsCounted(new StringValue("a"), new StringValue("b"), new StringValue("c")), + ); assert.deepEqual(bundle.values[2], new BigUIntValue(42)); }); @@ -164,14 +185,12 @@ describe("test smart contract results parser", () => { let endpointModifiers = new EndpointModifiers("", []); let outputParameters = [ new EndpointParameterDefinition("a", "a", new BigUIntType()), - new EndpointParameterDefinition("b", "b", new BytesType()) + new EndpointParameterDefinition("b", "b", new BytesType()), ]; let endpoint = new EndpointDefinition("foo", [], outputParameters, endpointModifiers); let transactionOnNetwork = new TransactionOnNetwork({ - contractResults: new ContractResults([ - new ContractResultItem({ nonce: 7, data: "@6f6b@2a@abba" }) - ]) + contractResults: new ContractResults([new ContractResultItem({ nonce: 7, data: "@6f6b@2a@abba" })]), }); let bundle = parser.parseOutcome(transactionOnNetwork, endpoint); @@ -188,9 +207,9 @@ describe("test smart contract results parser", () => { new ContractResultItem({ nonce: 42, data: "@6f6b@03", - returnMessage: "foobar" - }) - ]) + returnMessage: "foobar", + }), + ]), }); let bundle = parser.parseUntypedOutcome(transaction); @@ -207,10 +226,10 @@ describe("test smart contract results parser", () => { new TransactionEvent({ identifier: "signalError", topics: [new TransactionEventTopic(Buffer.from("something happened").toString("base64"))], - data: `@${Buffer.from("user error").toString("hex")}@07` - }) - ] - }) + data: `@${Buffer.from("user error").toString("hex")}@07`, + }), + ], + }), }); let bundle = parser.parseUntypedOutcome(transaction); @@ -226,16 +245,23 @@ describe("test smart contract results parser", () => { events: [ new TransactionEvent({ identifier: "writeLog", - topics: [new TransactionEventTopic("QHRvbyBtdWNoIGdhcyBwcm92aWRlZCBmb3IgcHJvY2Vzc2luZzogZ2FzIHByb3ZpZGVkID0gNTk2Mzg0NTAwLCBnYXMgdXNlZCA9IDczMzAxMA==")], - data: Buffer.from("QDZmNmI=", "base64").toString() - }) - ] - }) + topics: [ + new TransactionEventTopic( + "QHRvbyBtdWNoIGdhcyBwcm92aWRlZCBmb3IgcHJvY2Vzc2luZzogZ2FzIHByb3ZpZGVkID0gNTk2Mzg0NTAwLCBnYXMgdXNlZCA9IDczMzAxMA==", + ), + ], + data: Buffer.from("QDZmNmI=", "base64").toString(), + }), + ], + }), }); let bundle = parser.parseUntypedOutcome(transaction); assert.deepEqual(bundle.returnCode, ReturnCode.Ok); - assert.equal(bundle.returnMessage, "@too much gas provided for processing: gas provided = 596384500, gas used = 733010"); + assert.equal( + bundle.returnMessage, + "@too much gas provided for processing: gas provided = 596384500, gas used = 733010", + ); assert.deepEqual(bundle.values, []); }); @@ -249,12 +275,15 @@ describe("test smart contract results parser", () => { new TransactionEventTopic("cmzC1LRt1r10pMhNAnFb+FyudjGMq4G8CefCYdQUmmc="), new TransactionEventTopic("AAAADFdFR0xELTAxZTQ5ZAAAAAAAAAAAAAAAAWQ="), ], - dataPayload: new TransactionEventData(Buffer.from("AAAAAAAAA9sAAAA=", "base64")) + dataPayload: new TransactionEventData(Buffer.from("AAAAAAAAA9sAAAA=", "base64")), }); const bundle = parser.parseEvent(event, eventDefinition); - assert.equal((bundle.dest_address).bech32(), "erd1wfkv9495dhtt6a9yepxsyu2mlpw2ua333j4cr0qfulpxr4q5nfnshgyqun"); + assert.equal( + (bundle.dest_address).bech32(), + "erd1wfkv9495dhtt6a9yepxsyu2mlpw2ua333j4cr0qfulpxr4q5nfnshgyqun", + ); assert.equal(bundle.tokens[0].token_identifier, "WEGLD-01e49d"); assert.deepEqual(bundle.tokens[0].token_nonce, new BigNumber(0)); assert.deepEqual(bundle.tokens[0].amount, new BigNumber(100)); @@ -266,28 +295,28 @@ describe("test smart contract results parser", () => { it("should parse contract event (with multi-values)", async () => { const abiRegistry = AbiRegistry.create({ - "events": [ + events: [ { - "identifier": "foobar", - "inputs": [ + identifier: "foobar", + inputs: [ { - "name": "a", - "type": "multi", - "indexed": true + name: "a", + type: "multi", + indexed: true, }, { - "name": "b", - "type": "multi", - "indexed": true + name: "b", + type: "multi", + indexed: true, }, { - "name": "c", - "type": "u8", - "indexed": false - } - ] - } - ] + name: "c", + type: "u8", + indexed: false, + }, + ], + }, + ], }); const eventDefinition = abiRegistry.getEvent("foobar"); @@ -302,7 +331,7 @@ describe("test smart contract results parser", () => { new TransactionEventTopic(Buffer.from("test").toString("base64")), new TransactionEventTopic(Buffer.from([44]).toString("base64")), ], - dataPayload: new TransactionEventData(Buffer.from([42])) + dataPayload: new TransactionEventData(Buffer.from([42])), }; const bundle = parser.parseEvent(event, eventDefinition); @@ -313,28 +342,28 @@ describe("test smart contract results parser", () => { it("should parse contract event (Sirius)", async () => { const abiRegistry = AbiRegistry.create({ - "events": [ + events: [ { - "identifier": "foobar", - "inputs": [ + identifier: "foobar", + inputs: [ { - "name": "a", - "type": "u8", - "indexed": true + name: "a", + type: "u8", + indexed: true, }, { - "name": "b", - "type": "u8", - "indexed": false + name: "b", + type: "u8", + indexed: false, }, { - "name": "c", - "type": "u8", - "indexed": false - } - ] - } - ] + name: "c", + type: "u8", + indexed: false, + }, + ], + }, + ], }); const eventDefinition = abiRegistry.getEvent("foobar"); @@ -344,10 +373,7 @@ describe("test smart contract results parser", () => { new TransactionEventTopic(Buffer.from("not used").toString("base64")), new TransactionEventTopic(Buffer.from([42]).toString("base64")), ], - additionalData: [ - new TransactionEventData(Buffer.from([43])), - new TransactionEventData(Buffer.from([44])) - ], + additionalData: [new TransactionEventData(Buffer.from([43])), new TransactionEventData(Buffer.from([44]))], // Will be ignored. dataPayload: new TransactionEventData(Buffer.from([43])), }; @@ -364,7 +390,7 @@ describe("test smart contract results parser", () => { let oldLogLevel = Logger.logLevel; Logger.setLevel(LogLevel.Trace); - let folder = path.resolve(process.env["SAMPLES"] || "SAMPLES") + let folder = path.resolve(process.env["SAMPLES"] || "SAMPLES"); let samples = loadRealWorldSamples(folder); for (const [transaction, _] of samples) { diff --git a/src/smartcontracts/smartContract.spec.ts b/src/smartcontracts/smartContract.spec.ts index 11ba86d7..090c63a9 100644 --- a/src/smartcontracts/smartContract.spec.ts +++ b/src/smartcontracts/smartContract.spec.ts @@ -1,7 +1,14 @@ import { TransactionStatus } from "@multiversx/sdk-network-providers"; import { assert } from "chai"; import { Address } from "../address"; -import { loadTestWallets, MarkCompleted, MockNetworkProvider, setupUnitTestWatcherTimeouts, TestWallet, Wait } from "../testutils"; +import { + loadTestWallets, + MarkCompleted, + MockNetworkProvider, + setupUnitTestWatcherTimeouts, + TestWallet, + Wait, +} from "../testutils"; import { TransactionWatcher } from "../transactionWatcher"; import { Code } from "./code"; import { ContractFunction } from "./function"; @@ -9,7 +16,6 @@ import { SmartContract } from "./smartContract"; import { U32Value } from "./typesystem"; import { BytesValue } from "./typesystem/bytes"; - describe("test contract", () => { let provider = new MockNetworkProvider(); let chainID = "test"; @@ -38,10 +44,10 @@ describe("test contract", () => { code: Code.fromBuffer(Buffer.from([1, 2, 3, 4])), gasLimit: 1000000, chainID: chainID, - deployer: alice.address + deployer: alice.address, }); - provider.mockUpdateAccount(alice.address, account => { + provider.mockUpdateAccount(alice.address, (account) => { account.nonce = 42; }); @@ -63,8 +69,14 @@ describe("test contract", () => { let hash = await provider.sendTransaction(deployTransaction); await Promise.all([ - provider.mockTransactionTimeline(deployTransaction, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), - watcher.awaitCompleted(deployTransaction.getHash().hex()) + provider.mockTransactionTimeline(deployTransaction, [ + new Wait(40), + new TransactionStatus("pending"), + new Wait(40), + new TransactionStatus("executed"), + new MarkCompleted(), + ]), + watcher.awaitCompleted(deployTransaction.getHash().hex()), ]); assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted()); @@ -74,10 +86,12 @@ describe("test contract", () => { setupUnitTestWatcherTimeouts(); let watcher = new TransactionWatcher(provider); - let contract = new SmartContract({ address: new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3") }); + let contract = new SmartContract({ + address: new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"), + }); - provider.mockUpdateAccount(alice.address, account => { - account.nonce = 42 + provider.mockUpdateAccount(alice.address, (account) => { + account.nonce = 42; }); let callTransactionOne = contract.call({ @@ -85,7 +99,7 @@ describe("test contract", () => { args: [new U32Value(5), BytesValue.fromHex("0123")], gasLimit: 150000, chainID: chainID, - caller: alice.address + caller: alice.address, }); let callTransactionTwo = contract.call({ @@ -93,7 +107,7 @@ describe("test contract", () => { args: [new U32Value(5), BytesValue.fromHex("0123")], gasLimit: 1500000, chainID: chainID, - caller: alice.address + caller: alice.address, }); await alice.sync(provider); @@ -116,10 +130,22 @@ describe("test contract", () => { let hashTwo = await provider.sendTransaction(callTransactionTwo); await Promise.all([ - provider.mockTransactionTimeline(callTransactionOne, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), - provider.mockTransactionTimeline(callTransactionTwo, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), + provider.mockTransactionTimeline(callTransactionOne, [ + new Wait(40), + new TransactionStatus("pending"), + new Wait(40), + new TransactionStatus("executed"), + new MarkCompleted(), + ]), + provider.mockTransactionTimeline(callTransactionTwo, [ + new Wait(40), + new TransactionStatus("pending"), + new Wait(40), + new TransactionStatus("executed"), + new MarkCompleted(), + ]), watcher.awaitCompleted(callTransactionOne.getHash().hex()), - watcher.awaitCompleted(callTransactionTwo.getHash().hex()) + watcher.awaitCompleted(callTransactionTwo.getHash().hex()), ]); assert.isTrue((await provider.getTransactionStatus(hashOne)).isExecuted()); @@ -131,16 +157,16 @@ describe("test contract", () => { let watcher = new TransactionWatcher(provider); let contract = new SmartContract(); - contract.setAddress(Address.fromBech32("erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q")) + contract.setAddress(Address.fromBech32("erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q")); let deployTransaction = contract.upgrade({ code: Code.fromBuffer(Buffer.from([1, 2, 3, 4])), gasLimit: 1000000, chainID: chainID, - caller: alice.address + caller: alice.address, }); - provider.mockUpdateAccount(alice.address, account => { + provider.mockUpdateAccount(alice.address, (account) => { account.nonce = 42; }); @@ -158,8 +184,14 @@ describe("test contract", () => { let hash = await provider.sendTransaction(deployTransaction); await Promise.all([ - provider.mockTransactionTimeline(deployTransaction, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), - watcher.awaitCompleted(deployTransaction.getHash().hex()) + provider.mockTransactionTimeline(deployTransaction, [ + new Wait(40), + new TransactionStatus("pending"), + new Wait(40), + new TransactionStatus("executed"), + new MarkCompleted(), + ]), + watcher.awaitCompleted(deployTransaction.getHash().hex()), ]); assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted()); diff --git a/src/smartcontracts/transactionPayloadBuilders.spec.ts b/src/smartcontracts/transactionPayloadBuilders.spec.ts index 6e98fe82..884c48b0 100644 --- a/src/smartcontracts/transactionPayloadBuilders.spec.ts +++ b/src/smartcontracts/transactionPayloadBuilders.spec.ts @@ -4,8 +4,11 @@ import { ContractFunction } from "./function"; import { Code } from "./code"; import { CodeMetadata } from "./codeMetadata"; import { AddressValue, U32Value } from "./typesystem"; -import { ContractCallPayloadBuilder, ContractDeployPayloadBuilder, ContractUpgradePayloadBuilder } from "./transactionPayloadBuilders"; - +import { + ContractCallPayloadBuilder, + ContractDeployPayloadBuilder, + ContractUpgradePayloadBuilder, +} from "./transactionPayloadBuilders"; describe("test contract payload builders", () => { it("should prepare deploy correctly", async () => { @@ -36,9 +39,9 @@ describe("test contract payload builders", () => { .addArg(new U32Value(1024)) .build(); - assert.equal(payload.valueOf().toString(), "transferToken@fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293@0400"); + assert.equal( + payload.valueOf().toString(), + "transferToken@fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293@0400", + ); }); }); - - - From ce867f9f1536c808fd4ac390949ac89629b6cf17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 17 Apr 2024 23:10:57 +0300 Subject: [PATCH 13/16] Bit of formatting. --- src/smartcontracts/nativeSerializer.ts | 88 +++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/src/smartcontracts/nativeSerializer.ts b/src/smartcontracts/nativeSerializer.ts index 7e08cc37..e99ed2db 100644 --- a/src/smartcontracts/nativeSerializer.ts +++ b/src/smartcontracts/nativeSerializer.ts @@ -1,14 +1,67 @@ +/* eslint-disable @typescript-eslint/no-namespace */ import BigNumber from "bignumber.js"; import { Address } from "../address"; import { ErrInvalidArgument } from "../errors"; import { IAddress } from "../interface"; import { numberToPaddedHex } from "../utils.codec"; import { ArgumentErrorContext } from "./argumentErrorContext"; -import { AddressType, AddressValue, BigIntType, BigIntValue, BigUIntType, BigUIntValue, BooleanType, BooleanValue, BytesType, BytesValue, CompositeType, CompositeValue, EndpointDefinition, EndpointParameterDefinition, EnumType, EnumValue, Field, I16Type, I16Value, I32Type, I32Value, I64Type, I64Value, I8Type, I8Value, List, ListType, NumericalType, OptionalType, OptionalValue, OptionType, OptionValue, PrimitiveType, Struct, StructType, TokenIdentifierType, TokenIdentifierValue, Tuple, TupleType, Type, TypedValue, U16Type, U16Value, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, VariadicType, VariadicValue } from "./typesystem"; +import { + AddressType, + AddressValue, + BigIntType, + BigIntValue, + BigUIntType, + BigUIntValue, + BooleanType, + BooleanValue, + BytesType, + BytesValue, + CompositeType, + CompositeValue, + EndpointDefinition, + EndpointParameterDefinition, + EnumType, + EnumValue, + Field, + I16Type, + I16Value, + I32Type, + I32Value, + I64Type, + I64Value, + I8Type, + I8Value, + List, + ListType, + NumericalType, + OptionalType, + OptionalValue, + OptionType, + OptionValue, + PrimitiveType, + Struct, + StructType, + TokenIdentifierType, + TokenIdentifierValue, + Tuple, + TupleType, + Type, + TypedValue, + U16Type, + U16Value, + U32Type, + U32Value, + U64Type, + U64Value, + U8Type, + U8Value, + VariadicType, + VariadicValue, +} from "./typesystem"; export namespace NativeTypes { export type NativeBuffer = Buffer | string; - export type NativeBytes = Buffer | { valueOf(): Buffer; } | string; + export type NativeBytes = Buffer | { valueOf(): Buffer } | string; export type NativeAddress = string | Buffer | IAddress | { getAddress(): IAddress }; } @@ -46,7 +99,9 @@ export namespace NativeSerializer { const { min, max } = getArgumentsCardinality(endpoint.input); if (!(min <= args.length && args.length <= max)) { - throw new ErrInvalidArgument(`Wrong number of arguments for endpoint ${endpoint.name}: expected between ${min} and ${max} arguments, have ${args.length}`); + throw new ErrInvalidArgument( + `Wrong number of arguments for endpoint ${endpoint.name}: expected between ${min} and ${max} arguments, have ${args.length}`, + ); } } @@ -67,7 +122,9 @@ export namespace NativeSerializer { if (argAtIndex?.belongsToTypesystem) { const isVariadicValue = argAtIndex.hasClassOrSuperclass(VariadicValue.ClassName); if (!isVariadicValue) { - throw new ErrInvalidArgument(`Wrong argument type for endpoint ${endpoint.name}: typed value provided; expected variadic type, have ${argAtIndex.getClassName()}`); + throw new ErrInvalidArgument( + `Wrong argument type for endpoint ${endpoint.name}: typed value provided; expected variadic type, have ${argAtIndex.getClassName()}`, + ); } // Do not repack. @@ -82,21 +139,31 @@ export namespace NativeSerializer { // f(arg1, arg2, optional, optional) returns { min: 2, max: 4, variadic: false } // f(arg1, variadic) returns { min: 1, max: Infinity, variadic: true } // f(arg1, arg2, optional, arg4, optional, variadic) returns { min: 2, max: Infinity, variadic: true } - function getArgumentsCardinality(parameters: EndpointParameterDefinition[]): { min: number, max: number, variadic: boolean } { + export function getArgumentsCardinality(parameters: EndpointParameterDefinition[]): { + min: number; + max: number; + variadic: boolean; + } { let reversed = [...parameters].reverse(); // keep the original unchanged let min = parameters.length; let max = parameters.length; let variadic = false; + if (reversed.length > 0 && reversed[0].type.getCardinality().isComposite()) { max = Infinity; variadic = true; } + for (let parameter of reversed) { + // It's a single-value, not a multi-value parameter. Thus, cardinality isn't affected. if (parameter.type.getCardinality().isSingular()) { break; } + + // It's a multi-value parameter: optional, variadic etc. min -= 1; } + return { min, max, variadic }; } @@ -154,7 +221,9 @@ export namespace NativeSerializer { function toVariadicValue(native: any, type: VariadicType, errorContext: ArgumentErrorContext): TypedValue { if (type.isCounted) { - throw new ErrInvalidArgument(`Counted variadic arguments must be explicitly typed. E.g. use "VariadicValue.fromItemsCounted()" or "new VariadicValue()"`); + throw new ErrInvalidArgument( + `Counted variadic arguments must be explicitly typed. E.g. use "VariadicValue.fromItemsCounted()" or "new VariadicValue()"`, + ); } if (native == null) { @@ -278,7 +347,7 @@ export namespace NativeSerializer { return new BytesValue(innerValue); } if (typeof innerValue === "number") { - return BytesValue.fromHex(numberToPaddedHex(innerValue)) + return BytesValue.fromHex(numberToPaddedHex(innerValue)); } errorContext.convertError(native, "BytesValue"); @@ -299,7 +368,10 @@ export namespace NativeSerializer { } // TODO: move logic to typesystem/address.ts - export function convertNativeToAddress(native: NativeTypes.NativeAddress, errorContext: ArgumentErrorContext): IAddress { + export function convertNativeToAddress( + native: NativeTypes.NativeAddress, + errorContext: ArgumentErrorContext, + ): IAddress { if ((native).bech32) { return native; } From 0c37c6b3d45d41e4cda587d930f7cfe8d5ee41df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 17 Apr 2024 23:11:48 +0300 Subject: [PATCH 14/16] Unit test for "getArgumentsCardinality". --- src/smartcontracts/nativeSerializer.spec.ts | 64 +++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/smartcontracts/nativeSerializer.spec.ts b/src/smartcontracts/nativeSerializer.spec.ts index 257c9267..221f7b05 100644 --- a/src/smartcontracts/nativeSerializer.spec.ts +++ b/src/smartcontracts/nativeSerializer.spec.ts @@ -518,4 +518,68 @@ describe("test native serializer", () => { assert.deepEqual(typedValues[3].getType(), enumType); assert.deepEqual(typedValues[3].valueOf(), { name: "Else", fields: [new BigNumber(42), new BigNumber(43)] }); }); + + it("should getArgumentsCardinality", async () => { + const abi = AbiRegistry.create({ + endpoints: [ + { + name: "a", + inputs: [ + { + type: "u8", + }, + ], + }, + { + name: "b", + inputs: [ + { + type: "variadic", + }, + ], + }, + { + name: "c", + inputs: [ + { + type: "optional", + }, + ], + }, + { + name: "d", + inputs: [ + { + type: "optional>", + }, + ], + }, + ], + }); + + assert.deepEqual(NativeSerializer.getArgumentsCardinality(abi.getEndpoint("a").input), { + min: 1, + max: 1, + variadic: false, + }); + + assert.deepEqual(NativeSerializer.getArgumentsCardinality(abi.getEndpoint("b").input), { + min: 0, + max: Infinity, + variadic: true, + }); + + assert.deepEqual(NativeSerializer.getArgumentsCardinality(abi.getEndpoint("c").input), { + min: 0, + max: 1, + variadic: false, + }); + + assert.deepEqual(NativeSerializer.getArgumentsCardinality(abi.getEndpoint("d").input), { + min: 0, + max: 1, + // This is somehow a limitation of the current implementation. + variadic: false, + }); + }); }); From a5411b2d7ab2c933fed6f05c7106209d709ba92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 18 Apr 2024 10:16:59 +0300 Subject: [PATCH 15/16] Additional testing & debugging. --- src/smartcontracts/smartContract.spec.ts | 69 +++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/smartcontracts/smartContract.spec.ts b/src/smartcontracts/smartContract.spec.ts index 090c63a9..aed6f381 100644 --- a/src/smartcontracts/smartContract.spec.ts +++ b/src/smartcontracts/smartContract.spec.ts @@ -13,7 +13,7 @@ import { TransactionWatcher } from "../transactionWatcher"; import { Code } from "./code"; import { ContractFunction } from "./function"; import { SmartContract } from "./smartContract"; -import { U32Value } from "./typesystem"; +import { AbiRegistry, OptionalValue, U32Value, U8Value, VariadicValue } from "./typesystem"; import { BytesValue } from "./typesystem/bytes"; describe("test contract", () => { @@ -196,4 +196,71 @@ describe("test contract", () => { assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted()); }); + + it("should be stricter than v12 on optional (exotic) parameters (since NativeSerializer is used under the hood)", async () => { + // Related to: https://github.com/multiversx/mx-sdk-js-core/issues/435. + // In v12, contract.call() only supported TypedValue[] as contract call arguments. + // In v13, NativeSerializer is used under the hood, which allows one to mix typed values with native values. + // However, this comes with additional rules regarding optional> parameters. + // These parameters are exotic and, generally speaking, can be avoided in contracts: + // https://docs.multiversx.com/developers/data/multi-values/ + + const abi = AbiRegistry.create({ + endpoints: [ + { + name: "foo", + inputs: [ + { + type: "optional>", + }, + ], + }, + ], + }); + + const endpointFoo = abi.getEndpoint("foo"); + const callerAddress = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contractAddress = Address.fromBech32("erd1qqqqqqqqqqqqqpgqaxa53w6uk43n6dhyt2la6cd5lyv32qn4396qfsqlnk"); + + const contract = new SmartContract({ + abi, + address: contractAddress, + }); + + // This was possible in v12 (more permissive). + // In v12, contract.call() required TypedValue[] for "args". + assert.throws(() => { + contract.call({ + func: "foo", + args: [new U8Value(1), new U8Value(2), new U8Value(3)], + chainID: "D", + gasLimit: 1000000, + caller: callerAddress, + }); + }, "Wrong number of arguments for endpoint foo: expected between 0 and 1 arguments, have 3"); + + // In v13, the contract.call() would be as follows: + contract.call({ + func: "foo", + args: [[new U8Value(1), new U8Value(2), new U8Value(3)]], + chainID: "D", + gasLimit: 1000000, + caller: callerAddress, + }); + + // This did not work in v12, it does not work in v13 either (since it's imprecise / incorrect). + assert.throws(() => { + contract.methods.foo([1, 2, 3]); + }, "Wrong number of arguments for endpoint foo: expected between 0 and 1 arguments, have 3"); + + const optionalVariadicType = endpointFoo.input[0].type; + const variadicTypedValue = VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3)); + + // However, all these were and are still possible: + contract.methodsExplicit.foo([new U8Value(1), new U8Value(2), new U8Value(3)]); + contract.methods.foo([new OptionalValue(optionalVariadicType, variadicTypedValue)]); + contract.methods.foo([variadicTypedValue]); + contract.methods.foo([[new U8Value(1), new U8Value(2), new U8Value(3)]]); + contract.methods.foo([[new U8Value(1), 2, 3]]); + }); }); From fd5354cb213ddf108b03d4d2bc0d51cb3755e312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 18 Apr 2024 10:37:57 +0300 Subject: [PATCH 16/16] More tests with variadic arguments. --- src/smartcontracts/smartContract.spec.ts | 71 +++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/src/smartcontracts/smartContract.spec.ts b/src/smartcontracts/smartContract.spec.ts index aed6f381..7fc958b2 100644 --- a/src/smartcontracts/smartContract.spec.ts +++ b/src/smartcontracts/smartContract.spec.ts @@ -197,7 +197,7 @@ describe("test contract", () => { assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted()); }); - it("should be stricter than v12 on optional (exotic) parameters (since NativeSerializer is used under the hood)", async () => { + it("v13 should be stricter than v12 on optional> (exotic) parameters (since NativeSerializer is used under the hood)", async () => { // Related to: https://github.com/multiversx/mx-sdk-js-core/issues/435. // In v12, contract.call() only supported TypedValue[] as contract call arguments. // In v13, NativeSerializer is used under the hood, which allows one to mix typed values with native values. @@ -218,7 +218,6 @@ describe("test contract", () => { ], }); - const endpointFoo = abi.getEndpoint("foo"); const callerAddress = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contractAddress = Address.fromBech32("erd1qqqqqqqqqqqqqpgqaxa53w6uk43n6dhyt2la6cd5lyv32qn4396qfsqlnk"); @@ -248,11 +247,21 @@ describe("test contract", () => { caller: callerAddress, }); + // Or simply: + contract.call({ + func: "foo", + args: [[1, 2, 3]], + chainID: "D", + gasLimit: 1000000, + caller: callerAddress, + }); + // This did not work in v12, it does not work in v13 either (since it's imprecise / incorrect). assert.throws(() => { contract.methods.foo([1, 2, 3]); }, "Wrong number of arguments for endpoint foo: expected between 0 and 1 arguments, have 3"); + const endpointFoo = abi.getEndpoint("foo"); const optionalVariadicType = endpointFoo.input[0].type; const variadicTypedValue = VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3)); @@ -263,4 +272,62 @@ describe("test contract", () => { contract.methods.foo([[new U8Value(1), new U8Value(2), new U8Value(3)]]); contract.methods.foo([[new U8Value(1), 2, 3]]); }); + + it("v13 should be stricter than v12 on variadic parameters (since NativeSerializer is used under the hood)", async () => { + const abi = AbiRegistry.create({ + endpoints: [ + { + name: "foo", + inputs: [ + { + type: "variadic", + }, + ], + }, + ], + }); + + const callerAddress = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contractAddress = Address.fromBech32("erd1qqqqqqqqqqqqqpgqaxa53w6uk43n6dhyt2la6cd5lyv32qn4396qfsqlnk"); + + const contract = new SmartContract({ + abi, + address: contractAddress, + }); + + // This was possible in v12 (more permissive). + // In v12, contract.call() required TypedValue[] for "args". + assert.throws(() => { + contract.call({ + func: "foo", + args: [new U8Value(1), new U8Value(2), new U8Value(3)], + chainID: "D", + gasLimit: 1000000, + caller: callerAddress, + }); + }, "Invalid argument: Wrong argument type for endpoint foo: typed value provided; expected variadic type, have U8Value"); + + // In v13, the contract.call() would be as follows: + contract.call({ + func: "foo", + args: [VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3))], + chainID: "D", + gasLimit: 1000000, + caller: callerAddress, + }); + + // Or simply: + contract.call({ + func: "foo", + args: [1, 2, 3], + chainID: "D", + gasLimit: 1000000, + caller: callerAddress, + }); + + // However, all these were and are still possible: + contract.methods.foo([1, 2, 3]); + contract.methodsExplicit.foo([new U8Value(1), new U8Value(2), new U8Value(3)]); + contract.methods.foo([VariadicValue.fromItems(new U8Value(1), new U8Value(2), new U8Value(3))]); + }); });