Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EN-13168: added relayed v1 tx builder #235

Merged
merged 11 commits into from
Sep 22, 2022
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class ErrTransactionWatcherTimeout extends Err {
}

/**
* Signals an issue related to waiting for a specific {@link TransactionStatus}.
* Signals an issue related to waiting for a specific transaction status.
*/
export class ErrExpectedTransactionStatusNotReached extends Err {
public constructor() {
Expand Down Expand Up @@ -303,6 +303,15 @@ export class ErrNotImplemented extends Err {
}
}

/**
* Signals invalid arguments when using the relayed v1 builder
*/
export class ErrInvalidRelayedV1BuilderArguments extends Err {
public constructor() {
super("invalid arguments for relayed v1 builder");
}
}

/**
* Signals invalid arguments when using the relayed v2 builder
*/
Expand Down
77 changes: 77 additions & 0 deletions src/relayedTransactionV1Builder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { loadTestWallets, TestWallet } from "./testutils";
import { assert} from "chai";
import * as errors from "./errors";
import { RelayedTransactionV1Builder } from "./relayedTransactionV1Builder";
import { Transaction } from "./transaction";
import { Address } from "./address";
import { TransactionPayload } from "./transactionPayload";

describe("test relayed v1 transaction builder", function () {
let alice: TestWallet, bob: TestWallet;

before(async function () {
({alice, bob} = await loadTestWallets());
});

it("should throw exception if args were not set", async function () {
const builder = new RelayedTransactionV1Builder();
assert.throw(() => builder.build(), errors.ErrInvalidRelayedV1BuilderArguments);

const innerTx = new Transaction({
nonce: 15,
sender: alice.address,
receiver: Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"),
gasLimit: 10000000,
chainID: "1",
data: new TransactionPayload("getContractConfig"),
});
builder.setInnerTransaction(innerTx);
assert.throw(() => builder.build(), errors.ErrInvalidRelayedV1BuilderArguments);

const networkConfig = {
MinGasLimit: 50_000,
GasPerDataByte: 1_500,
GasPriceModifier: 0.01,
ChainID: "T"
};
builder.setNetworkConfig(networkConfig);
assert.throw(() => builder.build(), errors.ErrInvalidRelayedV1BuilderArguments);

builder.setRelayerAddress(alice.getAddress());
assert.doesNotThrow(() => builder.build());
});

it("should compute relayed v1 transaction", async function () {
const networkConfig = {
MinGasLimit: 50_000,
GasPerDataByte: 1_500,
GasPriceModifier: 0.01,
ChainID: "T"
};

const innerTx = new Transaction({
nonce: 198,
sender: bob.address,
receiver: Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"),
gasLimit: 60000000,
chainID: networkConfig.ChainID,
data: new TransactionPayload("getContractConfig"),
});

await bob.signer.sign(innerTx);

const builder = new RelayedTransactionV1Builder();
const relayedTxV1 = builder
.setInnerTransaction(innerTx)
.setRelayerNonce(2627)
.setNetworkConfig(networkConfig)
.setRelayerAddress(alice.address)
.build();

await alice.signer.sign(relayedTxV1);

assert.equal(relayedTxV1.getNonce().valueOf(), 2627);
assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414141415141414141414141414141414141414141414141414141414141432f2f383d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a2239682b6e6742584f5536776674315464437368534d4b3454446a5a32794f74686336564c576e3478724d5a706248427738677a6c6659596d362b766b505258303764634a562b4745635462616a7049692b5a5a5942773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a317d");
assert.equal(relayedTxV1.getSignature().hex(), "c7d2c3b971f44eca676c10624d3c4319f8898af159f003e1e59f446cb75e5a294c9f0758d800e04d3daff11e67d20c4c1f85fd54aad6deb947ef391e6dd09d07");
});
});
113 changes: 113 additions & 0 deletions src/relayedTransactionV1Builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Transaction } from "./transaction";
import { IAddress, INonce } from "./interface";
import { INetworkConfig } from "./interfaceOfNetwork";
import { ErrInvalidRelayedV1BuilderArguments } from "./errors";
import { TransactionPayload } from "./transactionPayload";
import { ContractFunction, StringValue } from "./smartcontracts";
import { Address } from "./address";
import BigNumber from "bignumber.js";

export class RelayedTransactionV1Builder {
innerTransaction: Transaction | undefined;
relayerAddress: IAddress | undefined;
relayerNonce: INonce | undefined;
netConfig: INetworkConfig | undefined;

/**
* Sets the inner transaction to be used. It has to be already signed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

*
* @param {Transaction} transaction The inner transaction to be used
*/
setInnerTransaction(transaction: Transaction): RelayedTransactionV1Builder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have received an ITransaction here? Please leave as it is otherwise :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, we cannot use that

this.innerTransaction = transaction;
return this;
}

/**
* Sets the network config to be used for building the relayed v1 transaction
*
* @param {INetworkConfig} netConfig The network configuration to be used
*/
setNetworkConfig(netConfig: INetworkConfig): RelayedTransactionV1Builder {
this.netConfig = netConfig;
return this;
}

/**
* Sets the address of the relayer (the one that will actually pay the fee)
*
* @param relayerAddress
*/
setRelayerAddress(relayerAddress: IAddress): RelayedTransactionV1Builder {
this.relayerAddress = relayerAddress;
return this;
}

/**
* (optional) Sets the nonce of the relayer
*
* @param relayerNonce
*/
setRelayerNonce(relayerNonce: INonce) : RelayedTransactionV1Builder {
this.relayerNonce = relayerNonce;
return this;
}

/**
* Tries to build the relayed v1 transaction based on the previously set fields
*
* @throws ErrInvalidRelayedV1BuilderArguments
* @return Transaction
*/
build(): Transaction {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it's perfectly fine that we return the concrete Transaction, not an interface 👍

if (!this.innerTransaction || !this.netConfig || !this.relayerAddress || !this.innerTransaction.getSignature()) {
throw new ErrInvalidRelayedV1BuilderArguments();
}

const serializedTransaction = this.prepareInnerTransaction();
const payload = TransactionPayload.contractCall()
.setFunction(new ContractFunction("relayedTx"))
.setArgs([
new StringValue(serializedTransaction),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

])
.build();

const gasLimit = this.netConfig.MinGasLimit + this.netConfig.GasPerDataByte * payload.length() + this.innerTransaction.getGasLimit().valueOf();
let relayedTransaction = new Transaction({
nonce: this.relayerNonce,
sender: this.relayerAddress,
receiver: this.innerTransaction.getSender(),
value: 0,
gasLimit: gasLimit,
data: payload,
chainID: this.netConfig.ChainID,
});

if (this.relayerNonce) {
relayedTransaction.setNonce(this.relayerNonce);
}

return relayedTransaction;
}

private prepareInnerTransaction(): string {
if (!this.innerTransaction) {
return "";
}

const txObject = {
"nonce": this.innerTransaction.getNonce().valueOf(),
"sender": new Address(this.innerTransaction.getSender().bech32()).pubkey().toString("base64"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I've forgot about this 👍

"receiver": new Address(this.innerTransaction.getReceiver().bech32()).pubkey().toString("base64"),
"value": new BigNumber(this.innerTransaction.getValue().toString(), 10).toNumber(),
"gasPrice": this.innerTransaction.getGasPrice().valueOf(),
"gasLimit": this.innerTransaction.getGasLimit().valueOf(),
"data": this.innerTransaction.getData().valueOf().toString("base64"),
"signature": Buffer.from(this.innerTransaction.getSignature().hex(), 'hex').toString("base64"),
"chainID": Buffer.from(this.innerTransaction.getChainID().valueOf()).toString("base64"),
"version": this.innerTransaction.getVersion().valueOf(),
};

return JSON.stringify(txObject);
}
}
15 changes: 13 additions & 2 deletions src/relayedTransactionV2Builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,18 @@ describe("test relayed v2 transaction builder", function () {
chainID: networkConfig.ChainID,
data: new TransactionPayload("getContractConfig"),
});
builder = builder.setNetworkConfig(networkConfig).setInnerTransactionGasLimit(10).setInnerTransaction(innerTx);
builder = builder
.setNetworkConfig(networkConfig)
.setInnerTransactionGasLimit(10)
.setInnerTransaction(innerTx)
.setRelayerAddress(alice.address);
Comment on lines +39 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

assert.throw(() => builder.build(), errors.ErrGasLimitShouldBe0ForInnerTransaction);

innerTx.setGasLimit({ valueOf: function() { return 10; } });
innerTx.setGasLimit({
valueOf: function () {
return 10;
}
});
builder = builder.setNetworkConfig(networkConfig).setInnerTransactionGasLimit(10).setInnerTransaction(innerTx);
assert.throw(() => builder.build(), errors.ErrGasLimitShouldBe0ForInnerTransaction);
});
Expand Down Expand Up @@ -67,10 +75,13 @@ describe("test relayed v2 transaction builder", function () {
const relayedTxV2 = builder
.setInnerTransaction(innerTx)
.setInnerTransactionGasLimit(60_000_000)
.setRelayerNonce(37)
.setNetworkConfig(networkConfig)
.setRelayerAddress(alice.getAddress())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

.build();
await alice.signer.sign(relayedTxV2);

assert.equal(relayedTxV2.getNonce().valueOf(), 37);
assert.equal(
relayedTxV2.getData().toString(),
"relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@b6c5262d9837853e2201de357c1cc4c9803988a42d7049d26b7785dd0ac2bd4c6a8804b6fd9cf845fe2c2a622774b1a2dbd0a417c9a0bc3f0563a85bd15e710a");
Expand Down
Loading