Skip to content

Commit

Permalink
Merge pull request #366 from multiversx/transaction-next
Browse files Browse the repository at this point in the history
Implemented the Transaction class
  • Loading branch information
popenta authored Jan 5, 2024
2 parents 8f82766 + 55703be commit e059d13
Show file tree
Hide file tree
Showing 6 changed files with 529 additions and 19 deletions.
18 changes: 18 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,21 @@ export interface ITokenTransfer {
* @deprecated Use {@link ITokenTransfer} instead.
*/
export type ITokenPayment = ITokenTransfer;

export interface ITransactionNext {
sender: string;
receiver: string;
gasLimit: BigNumber.Value;
chainID: string;
nonce: BigNumber.Value;
value: BigNumber.Value;
senderUsername: string;
receiverUsername: string;
gasPrice: BigNumber.Value;
data: Uint8Array;
version: number;
options: number;
guardian: string;
signature: Uint8Array;
guardianSignature: Uint8Array;
}
2 changes: 1 addition & 1 deletion src/smartcontracts/interaction.local.net.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ describe("test smart contract interactor", function () {
const wallet = options.wallet;

const serialized = transaction.serializeForSigning();
const signature = await wallet.signerNext.sign(serialized);
const signature = await wallet.signer.sign(serialized);
transaction.applySignature(signature);
}
});
2 changes: 1 addition & 1 deletion src/transaction.local.net.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe("test transaction", function () {
const wallet = options.wallet;

const serialized = transaction.serializeForSigning();
const signature = await wallet.signerNext.sign(serialized);
const signature = await wallet.signer.sign(serialized);
transaction.applySignature(signature);
}
});
25 changes: 24 additions & 1 deletion src/transaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Address } from "./address";
import { TransactionOptions, TransactionVersion } from "./networkParams";
import { TestWallet, loadTestWallets } from "./testutils";
import { TokenTransfer } from "./tokenTransfer";
import { Transaction } from "./transaction";
import { Transaction, TransactionNext } from "./transaction";
import { TransactionPayload } from "./transactionPayload";
import { DraftTransaction } from "./draftTransaction";
import { TRANSACTION_MIN_GAS_PRICE } from "./constants";
Expand Down Expand Up @@ -40,6 +40,29 @@ describe("test transaction construction", async () => {
assert.deepEqual(transaction.getSignature(), Buffer.from([]));
});

it("create transaction from transaction next", async () => {
const plainTransactionNextObject = {
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
gasLimit: 56000,
value: "1000000000000000000",
data: Buffer.from("test"),
chainID: "T"
};
const nextTransaction = new TransactionNext(plainTransactionNextObject);

const transaction = Transaction.fromTransactionNext(nextTransaction);
assert.deepEqual(transaction.getSender(), Address.fromBech32(plainTransactionNextObject.sender));
assert.deepEqual(transaction.getReceiver(), Address.fromBech32(plainTransactionNextObject.receiver));
assert.equal(transaction.getGasLimit().valueOf(), plainTransactionNextObject.gasLimit);
assert.equal(transaction.getValue().toString(), plainTransactionNextObject.value);
assert.equal(transaction.getData().toString(), plainTransactionNextObject.data.toString());
assert.equal(transaction.getChainID().valueOf(), plainTransactionNextObject.chainID);
assert.equal(transaction.getNonce().valueOf(), 0);
assert.equal(transaction.getGasPrice().valueOf(), TRANSACTION_MIN_GAS_PRICE);
assert.deepEqual(transaction.getSignature(), Buffer.from([]));
});

it("with no data, no value", async () => {
let transaction = new Transaction({
nonce: 89,
Expand Down
278 changes: 262 additions & 16 deletions src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { BigNumber } from "bignumber.js";
import { Address } from "./address";
import { Compatibility } from "./compatibility";
import { TRANSACTION_MIN_GAS_PRICE } 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, ITransactionOptions, ITransactionPayload, ITransactionValue, ITransactionVersion } from "./interface";
import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, IPlainTransactionObject, ISignature, ITransactionNext, ITransactionOptions, ITransactionPayload, ITransactionValue, ITransactionVersion } from "./interface";
import { INetworkConfig } from "./interfaceOfNetwork";
import { TransactionOptions, TransactionVersion } from "./networkParams";
import { ProtoSerializer } from "./proto";
Expand Down Expand Up @@ -153,20 +153,6 @@ export class Transaction {
this.hash = TransactionHash.empty();
}

/**
* Creates a new Transaction object from a DraftTransaction.
*/
static fromDraft(draft: DraftTransaction): Transaction {
return new Transaction({
sender: Address.fromBech32(draft.sender),
receiver: Address.fromBech32(draft.receiver),
gasLimit: new BigNumber(draft.gasLimit).toNumber(),
chainID: "",
value: draft.value,
data: new TransactionPayload(Buffer.from(draft.data))
})
}

getNonce(): INonce {
return this.nonce;
}
Expand Down Expand Up @@ -435,6 +421,54 @@ export class Transaction {

return feeForMove.plus(processingFee);
}

/**
* Creates a new Transaction object from a DraftTransaction.
*/
static fromDraft(draft: DraftTransaction): Transaction {
return new Transaction({
sender: Address.fromBech32(draft.sender),
receiver: Address.fromBech32(draft.receiver),
gasLimit: new BigNumber(draft.gasLimit).toNumber(),
chainID: "",
value: draft.value,
data: new TransactionPayload(Buffer.from(draft.data))
})
}

/**
* Creates a new Transaction object from a TransactionNext object.
*/
static fromTransactionNext(transaction: ITransactionNext): Transaction {
const tx = new Transaction({
sender: Address.fromBech32(transaction.sender),
receiver: Address.fromBech32(transaction.receiver),
gasLimit: new BigNumber(transaction.gasLimit).toNumber(),
chainID: transaction.chainID,
value: new BigNumber(transaction.value).toFixed(0),
data: new TransactionPayload(Buffer.from(transaction.data)),
nonce: Number(transaction.nonce),
gasPrice: Number(transaction.gasPrice),
receiverUsername: transaction.receiverUsername,
senderUsername: transaction.senderUsername,
options: transaction.options,
version: transaction.version
});

if (transaction.guardian) {
tx.guardian = Address.fromBech32(transaction.guardian)
}

if (transaction.signature.length) {
tx.applySignature(transaction.signature);
}

if (transaction.guardianSignature.length) {
tx.applyGuardianSignature(transaction.guardianSignature);
}

return tx;
}
}

/**
Expand All @@ -458,3 +492,215 @@ export class TransactionHash extends Hash {
}
}

/**
* An abstraction for creating, signing and broadcasting transactions.
* Will replace the {@link Transaction} class in the future.
*/
export class TransactionNext{
/**
* The nonce of the transaction (the account sequence number of the sender).
*/
public nonce: BigNumber.Value;

/**
* The value to transfer.
*/
public value: BigNumber.Value;

/**
* The address of the sender.
*/
public sender: string;

/**
* The address of the receiver.
*/
public receiver: string;

/**
* The username of the sender.
*/
public senderUsername: string;

/**
* The username of the receiver.
*/
public receiverUsername: string;

/**
* The gas price to be used.
*/
public gasPrice: BigNumber.Value;

/**
* The maximum amount of gas to be consumed when processing the transaction.
*/
public gasLimit: BigNumber.Value;

/**
* The payload of the transaction.
*/
public data: Uint8Array;

/**
* The chain ID of the Network (e.g. "1" for Mainnet).
*/
public chainID: string;

/**
* The version, required by the Network in order to correctly interpret the contents of the transaction.
*/
public version: number;

/**
* The options field of the transactions.
*/
public options: number;

/**
* The address of the guardian.
*/
public guardian: string;

/**
* The signature.
*/
public signature: Uint8Array;

/**
* The signature of the guardian.
*/
public guardianSignature: Uint8Array;

/**
* Creates a new Transaction object.
*/
public constructor({
sender,
receiver,
gasLimit,
chainID,
nonce,
value,
senderUsername,
receiverUsername,
gasPrice,
data,
version,
options,
guardian,
}: {
nonce?: BigNumber.Value;
value?: BigNumber.Value;
sender: string;
receiver: string;
senderUsername?: string;
receiverUsername?: string;
gasPrice?: BigNumber.Value;
gasLimit: BigNumber.Value;
data?: Uint8Array;
chainID: string;
version?: number;
options?: number;
guardian?: string;
}) {
this.nonce = nonce || 0;
this.value = value || new BigNumber(0);
this.sender = sender;
this.receiver = receiver;
this.senderUsername = senderUsername || "";
this.receiverUsername = receiverUsername || "";
this.gasPrice = gasPrice || new BigNumber(TRANSACTION_MIN_GAS_PRICE);
this.gasLimit = gasLimit;
this.data = data || new Uint8Array();
this.chainID = chainID;
this.version = version || TRANSACTION_VERSION_DEFAULT;
this.options = options || TRANSACTION_OPTIONS_DEFAULT;
this.guardian = guardian || "";

this.signature = new Uint8Array();
this.guardianSignature = new Uint8Array();
}
}

/**
* An utilitary class meant to work together with the {@link TransactionNext} class.
*/
export class TransactionComputer {
constructor() {}

computeTransactionFee(transaction: ITransactionNext, networkConfig: INetworkConfig): BigNumber{
const moveBalanceGas = new BigNumber(
networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte);
if (moveBalanceGas > transaction.gasLimit) {
throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10));
}

const gasPrice = new BigNumber(transaction.gasPrice);
const feeForMove = moveBalanceGas.multipliedBy(gasPrice);
if (moveBalanceGas === transaction.gasLimit) {
return feeForMove;
}

const diff = new BigNumber(transaction.gasLimit).minus(moveBalanceGas);
const modifiedGasPrice = gasPrice.multipliedBy(
new BigNumber(networkConfig.GasPriceModifier)
);
const processingFee = diff.multipliedBy(modifiedGasPrice);

return feeForMove.plus(processingFee);
}

computeBytesForSigning(transaction: ITransactionNext): Uint8Array{
const plainTransaction = this.toPlainObject(transaction);

if (plainTransaction.signature) {
delete plainTransaction.signature;
}

if (plainTransaction.guardianSignature) {
delete plainTransaction.guardianSignature;
}

if (!plainTransaction.guardian) {
delete plainTransaction.guardian
}

const serialized = JSON.stringify(plainTransaction);

return new Uint8Array(Buffer.from(serialized));
}

computeTransactionHash(transaction: ITransactionNext): Uint8Array{
let serializer = new ProtoSerializer();

const tx = Transaction.fromTransactionNext(transaction);
const buffer = serializer.serializeTransaction(tx);
const hash = createTransactionHasher(TRANSACTION_HASH_LENGTH)
.update(buffer)
.digest("hex");

return Buffer.from(hash, "hex");
}

private toPlainObject(transaction: ITransactionNext) {
return {
nonce: Number(transaction.nonce),
value: new BigNumber(transaction.value).toFixed(0),
receiver: transaction.receiver,
sender: transaction.sender,
senderUsername: transaction.senderUsername ? Buffer.from(transaction.senderUsername).toString("base64") : undefined,
receiverUsername: transaction.receiverUsername ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined,
gasPrice: Number(transaction.gasPrice),
gasLimit: Number(transaction.gasLimit),
data: transaction.data && transaction.data.length === 0 ? undefined : Buffer.from(transaction.data).toString("base64"),
chainID: transaction.chainID,
version: transaction.version,
options: transaction.options ? transaction.options : undefined,
guardian: transaction.guardian ? transaction.guardian : undefined,
signature: transaction.signature.length == 0 ? undefined : Buffer.from(transaction.signature).toString("hex"),
guardianSignature: transaction.guardianSignature.length == 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex")
}
}

}
Loading

0 comments on commit e059d13

Please sign in to comment.