Skip to content

Commit

Permalink
Merge branch 'merge-main-in-feat-erdjs-v12' into guardedTransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
schimih committed Sep 28, 2022
2 parents cfaf305 + d8b149e commit 08870ef
Show file tree
Hide file tree
Showing 8 changed files with 598 additions and 312 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## 12.0.0
- [Breaking change: Implementation of topological sort to sort custom types by their type dependencies (change attribute visibility)](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/224)

## 11.1.0
- [Add builder for relayed v1 transactions](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/235)

## 11.0.1
- [Fix construction of AbiRegistry](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/234)

## 11.0.0
- [Breaking change: Sender is now mandatory when constructing a transaction](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/TBD)
- [Breaking change: Sender is now mandatory when constructing a transaction](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/226)
- Switched to MIT license

## 10.2.7
- [Update reference to keccak](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/222)

Expand Down
621 changes: 331 additions & 290 deletions package-lock.json

Large diffs are not rendered by default.

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
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export * from "./utils";
export * from "./tokenPayment";
export * from "./tokenTransferBuilders";
export * from "./smartcontracts";
export * from "./relayedTransactionV2Builder"
export * from "./relayedTransactionV1Builder";
export * from "./relayedTransactionV2Builder";
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.
*
* @param {Transaction} transaction The inner transaction to be used
*/
setInnerTransaction(transaction: Transaction): RelayedTransactionV1Builder {
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 {
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),
])
.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"),
"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);
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())
.build();
await alice.signer.sign(relayedTxV2);

assert.equal(relayedTxV2.getNonce().valueOf(), 37);
assert.equal(
relayedTxV2.getData().toString(),
"relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@bcf4d4aa206c649855aced3168db9684c77bcc0a9a75793bf64fef462bea17afe51625acd11bb9dd84b068fec7661e4d63c2b787f7235f37c237775c9867c105");
Expand Down
61 changes: 44 additions & 17 deletions src/relayedTransactionV2Builder.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {Transaction} from "./transaction";
import {TransactionPayload} from "./transactionPayload";
import {AddressValue, BytesValue, ContractFunction, U64Value} from "./smartcontracts";
import {IAddress, IChainID, IGasLimit, INonce, ITransactionPayload} from "./interface";
import {ISigner} from "@elrondnetwork/erdjs-walletcore/out/interface";
import {INetworkConfig} from "./interfaceOfNetwork";
import {ErrGasLimitShouldBe0ForInnerTransaction, ErrInvalidRelayedV2BuilderArguments} from "./errors";
import { Transaction } from "./transaction";
import { TransactionPayload } from "./transactionPayload";
import { AddressValue, BytesValue, ContractFunction, U64Value } from "./smartcontracts";
import { IAddress, IGasLimit, INonce } from "./interface";
import { INetworkConfig } from "./interfaceOfNetwork";
import { ErrGasLimitShouldBe0ForInnerTransaction, ErrInvalidRelayedV2BuilderArguments } from "./errors";

export class RelayedTransactionV2Builder {

innerTransaction: Transaction | undefined;
innerTransactionGasLimit: IGasLimit | undefined;
relayerAddress: IAddress | undefined;
relayerNonce: INonce | undefined;
netConfig: INetworkConfig | undefined;

/**
Expand All @@ -18,7 +18,7 @@ export class RelayedTransactionV2Builder {
*
* @param {Transaction} transaction The inner transaction to be used
*/
setInnerTransaction(transaction: Transaction) : RelayedTransactionV2Builder {
setInnerTransaction(transaction: Transaction): RelayedTransactionV2Builder {
this.innerTransaction = transaction;
return this;
}
Expand All @@ -29,7 +29,7 @@ export class RelayedTransactionV2Builder {
* @param {IGasLimit} gasLimit The gas limit to be used. The inner transaction needs to have the gas limit set to 0,
* so this field will specify the gas to be used for the SC call of the inner transaction
*/
setInnerTransactionGasLimit(gasLimit: IGasLimit) : RelayedTransactionV2Builder {
setInnerTransactionGasLimit(gasLimit: IGasLimit): RelayedTransactionV2Builder {
this.innerTransactionGasLimit = gasLimit;
return this;
}
Expand All @@ -39,23 +39,44 @@ export class RelayedTransactionV2Builder {
*
* @param {INetworkConfig} netConfig The network configuration to be used
*/
setNetworkConfig(netConfig: INetworkConfig) : RelayedTransactionV2Builder {
setNetworkConfig(netConfig: INetworkConfig): RelayedTransactionV2Builder {
this.netConfig = netConfig;
return this;
}

/**
* Tries to build the relayed v2 transaction based on the previously set fields
* Sets the address of the relayer (the one that will actually pay the fee)
*
* @param relayerAddress
*/
setRelayerAddress(relayerAddress: IAddress): RelayedTransactionV2Builder {
this.relayerAddress = relayerAddress;
return this;
}

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

/**
* Tries to build the relayed v2 transaction based on the previously set fields.
* It returns a transaction that isn't signed
*
* @throws ErrInvalidRelayedV2BuilderArguments
* @throws ErrGasLimitShouldBe0ForInnerTransaction
* @return Transaction
*/
build() : Transaction {
if(!this.innerTransaction || !this.innerTransactionGasLimit || !this.netConfig || !this.innerTransaction.getSignature()) {
build(): Transaction {
if (!this.innerTransaction || !this.innerTransactionGasLimit || !this.relayerAddress || !this.netConfig || !this.innerTransaction.getSignature()) {
throw new ErrInvalidRelayedV2BuilderArguments();
}
if(this.innerTransaction.getGasLimit() != 0){
if (this.innerTransaction.getGasLimit() != 0) {
throw new ErrGasLimitShouldBe0ForInnerTransaction();
}

Expand All @@ -69,14 +90,20 @@ export class RelayedTransactionV2Builder {
])
.build();

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

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

return relayedTransaction;
}
}

0 comments on commit 08870ef

Please sign in to comment.