From 07d93c98d0291bc110f1c534ea51024a158a88ce Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 20 Dec 2023 14:08:11 +0200 Subject: [PATCH 1/6] deprecate old relayed transactions builders --- src/errors.ts | 9 +++++++ src/relayedTransactionV1Builder.ts | 3 +++ src/relayedTransactionV2Builder.ts | 3 +++ .../transactionPayloadBuilders.ts | 3 +++ .../relayedTransactionsFactory.ts | 24 +++++++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 src/transactionsFactories/relayedTransactionsFactory.ts diff --git a/src/errors.ts b/src/errors.ts index 53d83bc6..caa1308a 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -352,3 +352,12 @@ export class ErrBadUsage extends Err { super(message); } } + +/** + * Signals an invalid inner transaction for relayed transactions + */ +export class ErrInvalidInnerTransaction extends Err{ + public constructor(message: string){ + super(message); + } +} \ No newline at end of file diff --git a/src/relayedTransactionV1Builder.ts b/src/relayedTransactionV1Builder.ts index 2e690e22..d87d84e5 100644 --- a/src/relayedTransactionV1Builder.ts +++ b/src/relayedTransactionV1Builder.ts @@ -7,6 +7,9 @@ import { TransactionOptions, TransactionVersion } from "./networkParams"; import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; +/** + * @deprecated Use {@link RelayedTransactionsFactory} instead. + */ export class RelayedTransactionV1Builder { innerTransaction: Transaction | undefined; relayerAddress: IAddress | undefined; diff --git a/src/relayedTransactionV2Builder.ts b/src/relayedTransactionV2Builder.ts index 2717e775..6cb1ce12 100644 --- a/src/relayedTransactionV2Builder.ts +++ b/src/relayedTransactionV2Builder.ts @@ -5,6 +5,9 @@ import { AddressValue, ArgSerializer, BytesValue, U64Value } from "./smartcontra import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; +/** + * @deprecated Use {@link RelayedTransactionsFactory} instead. + */ export class RelayedTransactionV2Builder { innerTransaction: Transaction | undefined; innerTransactionGasLimit: IGasLimit | undefined; diff --git a/src/smartcontracts/transactionPayloadBuilders.ts b/src/smartcontracts/transactionPayloadBuilders.ts index 07e65276..c70fbdcc 100644 --- a/src/smartcontracts/transactionPayloadBuilders.ts +++ b/src/smartcontracts/transactionPayloadBuilders.ts @@ -9,6 +9,9 @@ export const WasmVirtualMachine = "0500"; /** * A builder for {@link TransactionPayload} objects, to be used for Smart Contract deployment transactions. */ +/** + * @deprecated Use {@link SmartContractTransactionsFactory} instead. + */ export class ContractDeployPayloadBuilder { private code: ICode | null = null; private codeMetadata: ICodeMetadata = ""; diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts new file mode 100644 index 00000000..61c37bc4 --- /dev/null +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -0,0 +1,24 @@ +import BigNumber from "bignumber.js"; +import { Transaction } from "../transaction"; +import { IAddress } from "../interface"; +import { ErrInvalidInnerTransaction } from "../errors"; + +interface IConfig { + chainID: string; + minGasLimit: BigNumber.Value; + gasLimitPerByte: BigNumber.Value; +} + +export class RelayedTransactionsFactory { + private readonly config: IConfig; + + constructor(config: IConfig) { + this.config = config; + } + + createRelayedV1Transaction(options: { innerTransaction: Transaction; relayerAddress: IAddress }) { + if (!options.innerTransaction.getGasLimit()) { + throw new ErrInvalidInnerTransaction("The gas limit is not set for the inner transaction"); + } + } +} From ed2bb69e64659258ecc67f4c45f9a64634f446be Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 3 Jan 2024 17:09:49 +0200 Subject: [PATCH 2/6] add method for creating relayed v1 transactions --- src/errors.ts | 2 +- src/transaction.ts | 4 +- .../relayedTransactionsFactory.spec.ts | 181 ++++++++++++++++++ .../relayedTransactionsFactory.ts | 60 +++++- 4 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 src/transactionsFactories/relayedTransactionsFactory.spec.ts diff --git a/src/errors.ts b/src/errors.ts index caa1308a..1f23fd1c 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -360,4 +360,4 @@ export class ErrInvalidInnerTransaction extends Err{ public constructor(message: string){ super(message); } -} \ No newline at end of file +} diff --git a/src/transaction.ts b/src/transaction.ts index 178744a2..899e4069 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -1,7 +1,7 @@ import { BigNumber } from "bignumber.js"; import { Address } from "./address"; import { Compatibility } from "./compatibility"; -import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_DEFAULT } from "./constants"; +import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_VERSION_DEFAULT } from "./constants"; import * as errors from "./errors"; import { Hash } from "./hash"; import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, IPlainTransactionObject, ISignature, ITransactionNext, ITransactionOptions, ITransactionPayload, ITransactionValue, ITransactionVersion } from "./interface"; @@ -614,7 +614,7 @@ export class TransactionNext{ this.gasLimit = gasLimit; this.data = data || new Uint8Array(); this.chainID = chainID; - this.version = version || TRANSACTION_OPTIONS_DEFAULT; + this.version = version || TRANSACTION_VERSION_DEFAULT; this.options = options || TRANSACTION_OPTIONS_DEFAULT; this.guardian = guardian || ""; diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts new file mode 100644 index 00000000..8737b363 --- /dev/null +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -0,0 +1,181 @@ +import { assert } from "chai"; +import { TestWallet, loadTestWallets } from "../testutils"; +import { TransactionComputer, TransactionNext } from "../transaction"; +import { RelayedTransactionsFactory } from "./relayedTransactionsFactory"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; +import BigNumber from "bignumber.js"; + +describe("test relayed v1 transaction builder", function () { + const config = new TransactionsFactoryConfig("T"); + const factory = new RelayedTransactionsFactory(config); + const transactionComputer = new TransactionComputer(); + let alice: TestWallet, bob: TestWallet, carol: TestWallet, grace: TestWallet, frank: TestWallet; + + before(async function () { + ({ alice, bob, carol, grace, frank } = await loadTestWallets()); + }); + + it("should throw exception when creating relayed v1 transaction with invalid inner transaction", async function () { + let innerTransaction = new TransactionNext({ + sender: alice.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gasLimit: 10000000, + data: Buffer.from("getContractConfig"), + chainID: config.chainID, + }); + + assert.throws(() => { + factory.createRelayedV1Transaction({ innerTransaction: innerTransaction, relayerAddress: bob.address }), + "The inner transaction is not signed"; + }); + + innerTransaction.gasLimit = 0; + innerTransaction.signature = Buffer.from("invalidsignature"); + + assert.throws(() => { + factory.createRelayedV1Transaction({ innerTransaction: innerTransaction, relayerAddress: bob.address }), + "The gas limit is not set for the inner transaction"; + }); + }); + + it("should create relayed v1 transaction", async function () { + let innerTransaction = new TransactionNext({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gasLimit: 60000000, + data: Buffer.from("getContractConfig"), + chainID: config.chainID, + nonce: 198, + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(Buffer.from(serializedInnerTransaction)); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 2627; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414141415141414141414141414141414141414141414141414141414141432f2f383d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a2272525455544858677a4273496e4f6e454b6b7869642b354e66524d486e33534948314673746f577352434c434b3258514c41614f4e704449346531476173624c5150616130566f364144516d4f2b52446b6f364a43413d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a327d" + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "128e7cdc14c2b9beee2f3ff7a7fa5d1f5ef31a654a0c92e223c90ab28265fa277d306f23a06536248cf9573e828017004fb639617fade4d68a37524aafca710d" + ); + }); + + it("should create relayed v1 transaction with usernames", async function () { + let innerTransaction = new TransactionNext({ + sender: carol.address.bech32(), + receiver: alice.address.bech32(), + gasLimit: 50000, + chainID: config.chainID, + nonce: 208, + senderUsername: "carol", + receiverUsername: "alice", + value: new BigNumber("1000000000000000000"), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await carol.signer.sign(Buffer.from(serializedInnerTransaction)); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: frank.address, + }); + relayedTransaction.nonce = 715; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await frank.signer.sign(Buffer.from(serializedRelayedTransaction)); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226a33427a6469554144325963517473576c65707663664a6f75657a48573063316b735a424a4d6339573167435450512b6870636759457858326f6f367a4b5654347464314b4b6f79783841526a346e336474576c44413d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d" + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "3787d640e5a579e7977a4a1bcdd435ad11855632fa4a414a06fbf8355692d1a58d76ef0adbdd6ccd6bd3c329f36bd53c180d4873ec1a6c558e659aeb9ab92d00" + ); + }); + + it("should create relayed v1 transaction with guarded inner transaction", async function () { + let innerTransaction = new TransactionNext({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqpgq54tsxmej537z9leghvp69hfu4f8gg5eu396q83gnnz", + gasLimit: 60000000, + chainID: config.chainID, + data: Buffer.from("getContractConfig"), + nonce: 198, + version: 2, + options: 2, + guardian: grace.address.bech32(), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(Buffer.from(serializedInnerTransaction)); + innerTransaction.guardianSignature = await grace.signer.sign(Buffer.from(serializedInnerTransaction)); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 2627; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a224b4b78324f33383655725135416b4f465258307578327933446a384853334b373038487174344668377161557669424550716c45614e746e6158706a6f2f333651476d4a456934784435457a6c6f4f677a634d4442773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a222b5431526f4833625a792f54423177342b6a365155477258645637457577553073753948646551626453515269463953757a686d634b705463526d58595252366c534c6652394931624d7134674730436538363741513d3d227d" + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "39cff9d5100e290fbc7361cb6e2402261caf864257b4116f150e0c61e7869155dff8361fa5449431eb7a8ed847c01ba9b3b5ebafe5fac1a3d40c64829d827e00" + ); + }); + + it("should create guarded relayed v1 transaction with guarded inner transaction", async function () { + let innerTransaction = new TransactionNext({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqpgq54tsxmej537z9leghvp69hfu4f8gg5eu396q83gnnz", + gasLimit: 60000000, + chainID: config.chainID, + data: Buffer.from("addNumber"), + nonce: 198, + version: 2, + options: 2, + guardian: grace.address.bech32(), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(Buffer.from(serializedInnerTransaction)); + innerTransaction.guardianSignature = await grace.signer.sign(Buffer.from(serializedInnerTransaction)); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 2627; + relayedTransaction.options = 2; + relayedTransaction.guardian = frank.address.bech32(); + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); + relayedTransaction.guardianSignature = await frank.signer.sign(Buffer.from(serializedRelayedTransaction)); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225957526b546e5674596d5679222c227369676e6174757265223a223469724d4b4a656d724d375174344e7635487633544c44683775654779487045564c4371674a3677652f7a662b746a4933354975573452633458543451533433475333356158386c6a533834324a38426854645043673d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a2270424754394e674a78307539624c56796b654d78786a454865374269696c37764932324a46676f32787a6e2f496e3032463769546563356b44395045324f747065386c475335412b532f4a36417762576834446744673d3d227d" + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "8ede1bbeed96b102344dffeac12c2592c62b7313cdeb132e8c8bf11d2b1d3bb8189d257a6dbcc99e222393d9b9ec77656c349dae97a32e68bdebd636066bf706" + ); + }); +}); diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index 61c37bc4..42495a16 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -1,7 +1,8 @@ import BigNumber from "bignumber.js"; -import { Transaction } from "../transaction"; +import { TransactionNext } from "../transaction"; import { IAddress } from "../interface"; import { ErrInvalidInnerTransaction } from "../errors"; +import { Address } from "../address"; interface IConfig { chainID: string; @@ -16,9 +17,62 @@ export class RelayedTransactionsFactory { this.config = config; } - createRelayedV1Transaction(options: { innerTransaction: Transaction; relayerAddress: IAddress }) { - if (!options.innerTransaction.getGasLimit()) { + createRelayedV1Transaction(options: { + innerTransaction: TransactionNext; + relayerAddress: IAddress; + }): TransactionNext { + if (!options.innerTransaction.gasLimit) { throw new ErrInvalidInnerTransaction("The gas limit is not set for the inner transaction"); } + + if (!options.innerTransaction.signature.length) { + throw new ErrInvalidInnerTransaction("The inner transaction is not signed"); + } + + const serializedTransaction = this.prepareInnerTransactionForRelayedV1(options.innerTransaction); + const data = `relayedTx@${Buffer.from(serializedTransaction).toString("hex")}`; + + const gasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy(new BigNumber(data.length)); + const gasLimit = new BigNumber(this.config.minGasLimit) + .plus(gasForDataLength) + .plus(new BigNumber(options.innerTransaction.gasLimit)); + + return new TransactionNext({ + chainID: this.config.chainID, + sender: options.relayerAddress.bech32(), + receiver: options.innerTransaction.sender, + gasLimit: gasLimit, + data: Buffer.from(data), + }); + } + + private prepareInnerTransactionForRelayedV1(innerTransaction: TransactionNext): string { + const txObject = { + nonce: new BigNumber(innerTransaction.nonce.toString(), 10).toNumber(), + sender: Address.fromBech32(innerTransaction.sender).pubkey().toString("base64"), + receiver: Address.fromBech32(innerTransaction.receiver).pubkey().toString("base64"), + value: new BigNumber(innerTransaction.value.toString(), 10).toNumber(), + gasPrice: new BigNumber(innerTransaction.gasPrice.toString(), 10).toNumber(), + gasLimit: new BigNumber(innerTransaction.gasLimit.toString(), 10).toNumber(), + data: Buffer.from(innerTransaction.data).toString("base64"), + signature: Buffer.from(innerTransaction.signature).toString("base64"), + chainID: Buffer.from(innerTransaction.chainID).toString("base64"), + version: innerTransaction.version, + options: innerTransaction.options.valueOf() == 0 ? undefined : innerTransaction.options, + guardian: innerTransaction.guardian + ? Address.fromBech32(innerTransaction.guardian).pubkey().toString("base64") + : undefined, + guardianSignature: innerTransaction.guardianSignature.length + ? Buffer.from(innerTransaction.guardianSignature).toString("base64") + : undefined, + sndUserName: innerTransaction.senderUsername + ? Buffer.from(innerTransaction.senderUsername).toString("base64") + : undefined, + rcvUserName: innerTransaction.receiverUsername + ? Buffer.from(innerTransaction.receiverUsername).toString("base64") + : undefined, + }; + + return JSON.stringify(txObject); } } From abfd4bcea53fff6adce2d19d006638597725c22d Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 3 Jan 2024 19:54:41 +0200 Subject: [PATCH 3/6] add method for creating relayed v2 transactions --- .../relayedTransactionsFactory.spec.ts | 62 +++++++++++++++++++ .../relayedTransactionsFactory.ts | 52 ++++++++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts index 8737b363..2c53bb04 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.spec.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -178,4 +178,66 @@ describe("test relayed v1 transaction builder", function () { "8ede1bbeed96b102344dffeac12c2592c62b7313cdeb132e8c8bf11d2b1d3bb8189d257a6dbcc99e222393d9b9ec77656c349dae97a32e68bdebd636066bf706" ); }); + + it("should throw exception when creating relayed v2 transaction with invalid inner transaction", async function () { + let innerTransaction = new TransactionNext({ + sender: bob.address.bech32(), + receiver: bob.address.bech32(), + gasLimit: 50000, + chainID: config.chainID, + }); + + assert.throws(() => { + factory.createRelayedV2Transaction({ + innerTransaction: innerTransaction, + innerTransactionGasLimit: 50000, + relayerAddress: carol.address, + }), + "The gas limit should not be set for the inner transaction"; + }); + + innerTransaction.gasLimit = 0; + + assert.throws(() => { + factory.createRelayedV2Transaction({ + innerTransaction: innerTransaction, + innerTransactionGasLimit: 50000, + relayerAddress: carol.address, + }), + "The inner transaction is not signed"; + }); + }); + + it("should create relayed v2 transaction", async function () { + let innerTransaction = new TransactionNext({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gasLimit: 0, + chainID: config.chainID, + data: Buffer.from("getContractConfig"), + nonce: 15, + version: 2, + options: 0, + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(Buffer.from(serializedInnerTransaction)); + + const relayedTransaction = factory.createRelayedV2Transaction({ + innerTransaction: innerTransaction, + innerTransactionGasLimit: new BigNumber("60000000"), + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 37; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); + + assert.equal(relayedTransaction.version, 2); + assert.equal(relayedTransaction.options, 0); + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@fc3ed87a51ee659f937c1a1ed11c1ae677e99629fae9cc289461f033e6514d1a8cfad1144ae9c1b70f28554d196bd6ba1604240c1c1dc19c959e96c1c3b62d0c" + ); + }); }); diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index 42495a16..7f66aff9 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -1,8 +1,9 @@ import BigNumber from "bignumber.js"; import { TransactionNext } from "../transaction"; -import { IAddress } from "../interface"; +import { IAddress, ITransactionNext } from "../interface"; import { ErrInvalidInnerTransaction } from "../errors"; import { Address } from "../address"; +import { AddressValue, ArgSerializer, BytesValue, U64Value } from "../smartcontracts"; interface IConfig { chainID: string; @@ -18,7 +19,7 @@ export class RelayedTransactionsFactory { } createRelayedV1Transaction(options: { - innerTransaction: TransactionNext; + innerTransaction: ITransactionNext; relayerAddress: IAddress; }): TransactionNext { if (!options.innerTransaction.gasLimit) { @@ -32,9 +33,11 @@ export class RelayedTransactionsFactory { const serializedTransaction = this.prepareInnerTransactionForRelayedV1(options.innerTransaction); const data = `relayedTx@${Buffer.from(serializedTransaction).toString("hex")}`; - const gasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy(new BigNumber(data.length)); + const additionalGasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy( + new BigNumber(data.length) + ); const gasLimit = new BigNumber(this.config.minGasLimit) - .plus(gasForDataLength) + .plus(additionalGasForDataLength) .plus(new BigNumber(options.innerTransaction.gasLimit)); return new TransactionNext({ @@ -46,6 +49,47 @@ export class RelayedTransactionsFactory { }); } + createRelayedV2Transaction(options: { + innerTransaction: ITransactionNext; + innerTransactionGasLimit: BigNumber.Value; + relayerAddress: IAddress; + }): TransactionNext { + if (options.innerTransaction.gasLimit) { + throw new ErrInvalidInnerTransaction("The gas limit should not be set for the inner transaction"); + } + + if (!options.innerTransaction.signature.length) { + throw new ErrInvalidInnerTransaction("The inner transaction is not signed"); + } + + const { argumentsString } = new ArgSerializer().valuesToString([ + new AddressValue(Address.fromBech32(options.innerTransaction.receiver)), + new U64Value(options.innerTransaction.nonce), + new BytesValue(Buffer.from(options.innerTransaction.data)), + new BytesValue(Buffer.from(options.innerTransaction.signature)), + ]); + + const data = `relayedTxV2@${argumentsString}`; + + const additionalGasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy( + new BigNumber(data.length) + ); + const gasLimit = new BigNumber(options.innerTransaction.gasLimit) + .plus(new BigNumber(this.config.minGasLimit)) + .plus(additionalGasForDataLength); + + return new TransactionNext({ + sender: options.relayerAddress.bech32(), + receiver: options.innerTransaction.sender, + value: 0, + gasLimit: gasLimit, + chainID: this.config.chainID, + data: Buffer.from(data), + version: options.innerTransaction.version, + options: options.innerTransaction.options, + }); + } + private prepareInnerTransactionForRelayedV1(innerTransaction: TransactionNext): string { const txObject = { nonce: new BigNumber(innerTransaction.nonce.toString(), 10).toNumber(), From 0b92e8511481ef336b89c041f1e08f2c637efe9b Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 8 Jan 2024 16:01:31 +0200 Subject: [PATCH 4/6] fix gas computation for relayed v2 --- src/transactionsFactories/relayedTransactionsFactory.spec.ts | 1 + src/transactionsFactories/relayedTransactionsFactory.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts index 2c53bb04..9dffd415 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.spec.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -235,6 +235,7 @@ describe("test relayed v1 transaction builder", function () { assert.equal(relayedTransaction.version, 2); assert.equal(relayedTransaction.options, 0); + assert.equal(relayedTransaction.gasLimit.toString(), "60414500"); assert.equal( Buffer.from(relayedTransaction.data).toString(), "relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@fc3ed87a51ee659f937c1a1ed11c1ae677e99629fae9cc289461f033e6514d1a8cfad1144ae9c1b70f28554d196bd6ba1604240c1c1dc19c959e96c1c3b62d0c" diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index 7f66aff9..74e86294 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -74,7 +74,7 @@ export class RelayedTransactionsFactory { const additionalGasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy( new BigNumber(data.length) ); - const gasLimit = new BigNumber(options.innerTransaction.gasLimit) + const gasLimit = new BigNumber(options.innerTransactionGasLimit) .plus(new BigNumber(this.config.minGasLimit)) .plus(additionalGasForDataLength); From c027a00517fe87835c1b732887cb6a922fd57c6a Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 19 Jan 2024 14:22:08 +0200 Subject: [PATCH 5/6] bugfixes and fixes after review --- package-lock.json | 11 ++++++ package.json | 1 + src/relayedTransactionV1Builder.spec.ts | 36 +++++++++++++++++++ src/relayedTransactionV1Builder.ts | 6 ++-- .../relayedTransactionsFactory.spec.ts | 34 ++++++++++++++++++ .../relayedTransactionsFactory.ts | 12 ++++--- 6 files changed, 93 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index db9905f8..2edd91a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", + "@types/json-bigint": "^1.0.4", "bech32": "1.1.4", "bignumber.js": "9.0.1", "blake2b": "2.1.3", @@ -645,6 +646,11 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "node_modules/@types/json-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", + "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==" + }, "node_modules/@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -4926,6 +4932,11 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "@types/json-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", + "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==" + }, "@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", diff --git a/package.json b/package.json index 5b98c647..c62d3184 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", + "@types/json-bigint": "^1.0.4", "bech32": "1.1.4", "bignumber.js": "9.0.1", "blake2b": "2.1.3", diff --git a/src/relayedTransactionV1Builder.spec.ts b/src/relayedTransactionV1Builder.spec.ts index 68248b8d..5695160f 100644 --- a/src/relayedTransactionV1Builder.spec.ts +++ b/src/relayedTransactionV1Builder.spec.ts @@ -114,6 +114,42 @@ describe("test relayed v1 transaction builder", function () { assert.equal(relayedTxV1.getSignature().toString("hex"), "3787d640e5a579e7977a4a1bcdd435ad11855632fa4a414a06fbf8355692d1a58d76ef0adbdd6ccd6bd3c329f36bd53c180d4873ec1a6c558e659aeb9ab92d00"); }); + it("should compute relayed v1 transaction with big value", async function () { + const networkConfig = { + MinGasLimit: 50_000, + GasPerDataByte: 1_500, + GasPriceModifier: 0.01, + ChainID: "T" + }; + + const innerTx = new Transaction({ + nonce: 208, + value: TokenTransfer.egldFromAmount(1999999), + sender: carol.address, + receiver: alice.address, + senderUsername: "carol", + receiverUsername: "alice", + gasLimit: 50000, + chainID: networkConfig.ChainID + }); + + innerTx.applySignature(await carol.signer.sign(innerTx.serializeForSigning())); + + const builder = new RelayedTransactionV1Builder(); + const relayedTxV1 = builder + .setInnerTransaction(innerTx) + .setRelayerNonce(715) + .setNetworkConfig(networkConfig) + .setRelayerAddress(frank.address) + .build(); + + relayedTxV1.applySignature(await frank.signer.sign(relayedTxV1.serializeForSigning())); + + assert.equal(relayedTxV1.getNonce().valueOf(), 715); + assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313939393939393030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a22594661677972512f726d614c7333766e7159307657553858415a7939354b4e31725738347a4f764b62376c7a3773576e2f566a546d68704378774d682b7261314e444832574d6f3965507648304f79427453776a44773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d"); + assert.equal(relayedTxV1.getSignature().toString("hex"), "c0fb5cf8c0a413d6988ba35dc279c63f8849572c5f23b1cab36dcc50952dc3ed9da01068d6ac0cbde7e14167bfc2eca5164d5c2154c89eb313c9c596e3f8b801"); + }); + it("should compute guarded inner Tx - relayed v1 transaction", async function () { const networkConfig = { MinGasLimit: 50_000, diff --git a/src/relayedTransactionV1Builder.ts b/src/relayedTransactionV1Builder.ts index d87d84e5..43a66d0d 100644 --- a/src/relayedTransactionV1Builder.ts +++ b/src/relayedTransactionV1Builder.ts @@ -7,6 +7,8 @@ import { TransactionOptions, TransactionVersion } from "./networkParams"; import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; +const JSONbig = require("json-bigint"); + /** * @deprecated Use {@link RelayedTransactionsFactory} instead. */ @@ -134,7 +136,7 @@ export class RelayedTransactionV1Builder { "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": new BigNumber(this.innerTransaction.getValue().toString(), 10).toNumber(), + "value": BigInt(this.innerTransaction.getValue().toString()), "gasPrice": this.innerTransaction.getGasPrice().valueOf(), "gasLimit": this.innerTransaction.getGasLimit().valueOf(), "data": this.innerTransaction.getData().valueOf().toString("base64"), @@ -148,6 +150,6 @@ export class RelayedTransactionV1Builder { "rcvUserName": this.innerTransaction.getReceiverUsername() ? Buffer.from(this.innerTransaction.getReceiverUsername()).toString("base64") : undefined, }; - return JSON.stringify(txObject); + return JSONbig.stringify(txObject); } } diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts index 9dffd415..50be725c 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.spec.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -104,6 +104,40 @@ describe("test relayed v1 transaction builder", function () { ); }); + it("should create relayed v1 transaction with big value", async function () { + let innerTransaction = new TransactionNext({ + sender: carol.address.bech32(), + receiver: alice.address.bech32(), + gasLimit: 50000, + chainID: config.chainID, + nonce: 208, + senderUsername: "carol", + receiverUsername: "alice", + value: new BigNumber("1999999000000000000000000"), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await carol.signer.sign(Buffer.from(serializedInnerTransaction)); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: frank.address, + }); + relayedTransaction.nonce = 715; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await frank.signer.sign(Buffer.from(serializedRelayedTransaction)); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313939393939393030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a22594661677972512f726d614c7333766e7159307657553858415a7939354b4e31725738347a4f764b62376c7a3773576e2f566a546d68704378774d682b7261314e444832574d6f3965507648304f79427453776a44773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d" + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "c0fb5cf8c0a413d6988ba35dc279c63f8849572c5f23b1cab36dcc50952dc3ed9da01068d6ac0cbde7e14167bfc2eca5164d5c2154c89eb313c9c596e3f8b801" + ); + }); + it("should create relayed v1 transaction with guarded inner transaction", async function () { let innerTransaction = new TransactionNext({ sender: bob.address.bech32(), diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index 74e86294..898c9e8b 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -5,6 +5,8 @@ import { ErrInvalidInnerTransaction } from "../errors"; import { Address } from "../address"; import { AddressValue, ArgSerializer, BytesValue, U64Value } from "../smartcontracts"; +const JSONbig = require("json-bigint"); + interface IConfig { chainID: string; minGasLimit: BigNumber.Value; @@ -92,12 +94,12 @@ export class RelayedTransactionsFactory { private prepareInnerTransactionForRelayedV1(innerTransaction: TransactionNext): string { const txObject = { - nonce: new BigNumber(innerTransaction.nonce.toString(), 10).toNumber(), + nonce: new BigNumber(innerTransaction.nonce).toNumber(), sender: Address.fromBech32(innerTransaction.sender).pubkey().toString("base64"), receiver: Address.fromBech32(innerTransaction.receiver).pubkey().toString("base64"), - value: new BigNumber(innerTransaction.value.toString(), 10).toNumber(), - gasPrice: new BigNumber(innerTransaction.gasPrice.toString(), 10).toNumber(), - gasLimit: new BigNumber(innerTransaction.gasLimit.toString(), 10).toNumber(), + value: BigInt(new BigNumber(innerTransaction.value).toFixed(0)), + gasPrice: new BigNumber(innerTransaction.gasPrice).toNumber(), + gasLimit: new BigNumber(innerTransaction.gasLimit).toNumber(), data: Buffer.from(innerTransaction.data).toString("base64"), signature: Buffer.from(innerTransaction.signature).toString("base64"), chainID: Buffer.from(innerTransaction.chainID).toString("base64"), @@ -117,6 +119,6 @@ export class RelayedTransactionsFactory { : undefined, }; - return JSON.stringify(txObject); + return JSONbig.stringify(txObject); } } From 06fbc6d4e7dd80f83727b8bbcb56b19427424df6 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 19 Jan 2024 14:27:11 +0200 Subject: [PATCH 6/6] update package.json --- package-lock.json | 14 +------------- package.json | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2edd91a2..8d649008 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,11 @@ "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", - "@types/json-bigint": "^1.0.4", "bech32": "1.1.4", "bignumber.js": "9.0.1", "blake2b": "2.1.3", "buffer": "6.0.3", + "json-bigint": "1.0.0", "json-duplicate-key-handle": "1.0.0", "keccak": "3.0.2", "protobufjs": "7.2.4" @@ -646,11 +646,6 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, - "node_modules/@types/json-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", - "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==" - }, "node_modules/@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -2695,7 +2690,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, "dependencies": { "bignumber.js": "^9.0.0" } @@ -4932,11 +4926,6 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, - "@types/json-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", - "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==" - }, "@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -6580,7 +6569,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, "requires": { "bignumber.js": "^9.0.0" } diff --git a/package.json b/package.json index c62d3184..5512cd1a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", - "@types/json-bigint": "^1.0.4", + "json-bigint": "1.0.0", "bech32": "1.1.4", "bignumber.js": "9.0.1", "blake2b": "2.1.3",