Skip to content

Commit

Permalink
Merge branch 'feat/next' into duplicate-local-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Mar 15, 2024
2 parents f0cbe0a + 52fdbf7 commit 56ae79d
Show file tree
Hide file tree
Showing 38 changed files with 1,777 additions and 1,457 deletions.
31 changes: 2 additions & 29 deletions package-lock.json

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

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-core",
"version": "13.0.0-beta.4",
"version": "13.0.0-beta.6",
"description": "MultiversX SDK for JavaScript and TypeScript",
"main": "out/index.js",
"types": "out/index.d.js",
Expand Down Expand Up @@ -31,7 +31,6 @@
"bech32": "1.1.4",
"blake2b": "2.1.3",
"buffer": "6.0.3",
"json-duplicate-key-handle": "1.0.0",
"keccak": "3.0.2",
"protobufjs": "7.2.4"
},
Expand Down
18 changes: 18 additions & 0 deletions src/abi/typeFormula.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export class TypeFormula {
name: string;
typeParameters: TypeFormula[];

constructor(name: string, typeParameters: TypeFormula[]) {
this.name = name;
this.typeParameters = typeParameters;
}

toString(): string {
if (this.typeParameters.length > 0) {
const typeParameters = this.typeParameters.map((typeParameter) => typeParameter.toString()).join(", ");
return `${this.name}<${typeParameters}>`;
} else {
return this.name;
}
}
}
25 changes: 25 additions & 0 deletions src/abi/typeFormulaParser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { assert } from "chai";
import { TypeFormulaParser } from "./typeFormulaParser";

describe("test type formula parser", () => {
it("should parse expression", async () => {
const parser = new TypeFormulaParser();

const testVectors = [
["i64", "i64"],
[" i64 ", "i64"],
["utf-8 string", "utf-8 string"],
["MultiResultVec<MultiResult2<Address, u64>>", "MultiResultVec<MultiResult2<Address, u64>>"],
["tuple3<i32, bytes, Option<i64>>", "tuple3<i32, bytes, Option<i64>>"],
["tuple2<i32, i32>", "tuple2<i32, i32>"],
["tuple2<i32,i32> ", "tuple2<i32, i32>"],
["tuple<List<u64>, List<u64>>", "tuple<List<u64>, List<u64>>"],
];

for (const [inputExpression, expectedExpression] of testVectors) {
const typeFormula = parser.parseExpression(inputExpression);
const outputExpression = typeFormula.toString();
assert.equal(outputExpression, expectedExpression);
}
});
});
130 changes: 130 additions & 0 deletions src/abi/typeFormulaParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { TypeFormula } from "./typeFormula";

export class TypeFormulaParser {
static BEGIN_TYPE_PARAMETERS = "<";
static END_TYPE_PARAMETERS = ">";
static COMMA = ",";
static PUNCTUATION = [
TypeFormulaParser.COMMA,
TypeFormulaParser.BEGIN_TYPE_PARAMETERS,
TypeFormulaParser.END_TYPE_PARAMETERS,
];

parseExpression(expression: string): TypeFormula {
expression = expression.trim();

const tokens = this.tokenizeExpression(expression).filter((token) => token !== TypeFormulaParser.COMMA);
const stack: any[] = [];

for (const token of tokens) {
if (this.isPunctuation(token)) {
if (this.isEndOfTypeParameters(token)) {
const typeFormula = this.acquireTypeWithParameters(stack);
stack.push(typeFormula);
} else if (this.isBeginningOfTypeParameters(token)) {
// This symbol is pushed as a simple string.
stack.push(token);
} else {
throw new Error(`Unexpected token (punctuation): ${token}`);
}
} else {
// It's a type name. We push it as a simple string.
stack.push(token);
}
}

if (stack.length !== 1) {
throw new Error(`Unexpected stack length at end of parsing: ${stack.length}`);
}
if (TypeFormulaParser.PUNCTUATION.includes(stack[0])) {
throw new Error("Unexpected root element.");
}

const item = stack[0];

if (item instanceof TypeFormula) {
return item;
} else if (typeof item === "string") {
// Expression contained a simple, non-generic type.
return new TypeFormula(item, []);
} else {
throw new Error(`Unexpected item on stack: ${item}`);
}
}

private tokenizeExpression(expression: string): string[] {
const tokens: string[] = [];
let currentToken = "";

for (const character of expression) {
if (this.isPunctuation(character)) {
if (currentToken) {
// Retain current token
tokens.push(currentToken.trim());
// Reset current token
currentToken = "";
}

// Punctuation character
tokens.push(character);
} else {
currentToken += character;
}
}

if (currentToken) {
// Retain the last token (if any).
tokens.push(currentToken.trim());
}

return tokens;
}

private acquireTypeWithParameters(stack: any[]): TypeFormula {
const typeParameters = this.acquireTypeParameters(stack);
const typeName = stack.pop();
const typeFormula = new TypeFormula(typeName, typeParameters.reverse());
return typeFormula;
}

private acquireTypeParameters(stack: any[]): TypeFormula[] {
const typeParameters: TypeFormula[] = [];

while (true) {
const item = stack.pop();

if (item === undefined) {
throw new Error("Badly specified type parameters");
}

if (this.isBeginningOfTypeParameters(item)) {
// We've acquired all type parameters.
break;
}

if (item instanceof TypeFormula) {
// Type parameter is a previously-acquired type.
typeParameters.push(item);
} else if (typeof item === "string") {
// Type parameter is a simple, non-generic type.
typeParameters.push(new TypeFormula(item, []));
} else {
throw new Error(`Unexpected type parameter object in stack: ${item}`);
}
}

return typeParameters;
}

private isPunctuation(token: string): boolean {
return TypeFormulaParser.PUNCTUATION.includes(token);
}

private isEndOfTypeParameters(token: string): boolean {
return token === TypeFormulaParser.END_TYPE_PARAMETERS;
}

private isBeginningOfTypeParameters(token: string): boolean {
return token === TypeFormulaParser.BEGIN_TYPE_PARAMETERS;
}
}
1 change: 1 addition & 0 deletions src/converters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TransactionsConverter } from "./transactionsConverter";
64 changes: 64 additions & 0 deletions src/converters/transactionsConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { IPlainTransactionObject, ITransaction } from "../interface";
import { Transaction } from "../transaction";

export class TransactionsConverter {
public transactionToPlainObject(transaction: ITransaction): IPlainTransactionObject {
const plainObject = {
nonce: Number(transaction.nonce),
value: transaction.value.toString(),
receiver: transaction.receiver,
sender: transaction.sender,
senderUsername: this.toBase64OrUndefined(transaction.senderUsername),
receiverUsername: this.toBase64OrUndefined(transaction.receiverUsername),
gasPrice: Number(transaction.gasPrice),
gasLimit: Number(transaction.gasLimit),
data: this.toBase64OrUndefined(transaction.data),
chainID: transaction.chainID.valueOf(),
version: transaction.version,
options: transaction.options == 0 ? undefined : transaction.options,
guardian: transaction.guardian ? transaction.guardian : undefined,
signature: this.toHexOrUndefined(transaction.signature),
guardianSignature: this.toHexOrUndefined(transaction.guardianSignature),
};

return plainObject;
}

private toBase64OrUndefined(value?: string | Uint8Array) {
return value && value.length ? Buffer.from(value).toString("base64") : undefined;
}

private toHexOrUndefined(value?: Uint8Array) {
return value && value.length ? Buffer.from(value).toString("hex") : undefined;
}

public plainObjectToTransaction(object: IPlainTransactionObject): Transaction {
const transaction = new Transaction({
nonce: BigInt(object.nonce),
value: BigInt(object.value || ""),
receiver: object.receiver,
receiverUsername: this.bufferFromBase64(object.receiverUsername).toString(),
sender: object.sender,
senderUsername: this.bufferFromBase64(object.senderUsername).toString(),
guardian: object.guardian,
gasPrice: BigInt(object.gasPrice),
gasLimit: BigInt(object.gasLimit),
data: this.bufferFromBase64(object.data),
chainID: String(object.chainID),
version: object.version,
options: object.options,
signature: this.bufferFromHex(object.signature),
guardianSignature: this.bufferFromHex(object.guardianSignature),
});

return transaction;
}

private bufferFromBase64(value?: string) {
return Buffer.from(value || "", "base64");
}

private bufferFromHex(value?: string) {
return Buffer.from(value || "", "hex");
}
}
23 changes: 23 additions & 0 deletions src/converters/transactionsConverters.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { assert } from "chai";
import { Transaction } from "../transaction";

describe("test transactions converter", async () => {
it("converts transaction to plain object and back", () => {
const transaction = new Transaction({
nonce: 90,
value: BigInt("123456789000000000000000000000"),
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
senderUsername: "alice",
receiverUsername: "bob",
gasPrice: 1000000000,
gasLimit: 80000,
data: Buffer.from("hello"),
chainID: "localnet",
});

const plainObject = transaction.toPlainObject();
const restoredTransaction = Transaction.fromPlainObject(plainObject);
assert.deepEqual(restoredTransaction, transaction);
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from "./transactionPayload";
export * from "./transactionWatcher";
export * from "./transferTransactionsFactory";
export * from "./utils";
export * from "./transactionComputer";
2 changes: 1 addition & 1 deletion src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export interface ITokenTransfer {
*/
export type ITokenPayment = ITokenTransfer;

export interface ITransactionNext {
export interface ITransaction {
sender: string;
receiver: string;
gasLimit: bigint;
Expand Down
2 changes: 2 additions & 0 deletions src/relayedTransactionV2Builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe("test relayed v2 transaction builder", function () {
gasLimit: 0,
chainID: networkConfig.ChainID,
data: new TransactionPayload("getContractConfig"),
version: 2,
});

innerTx.applySignature(await bob.signer.sign(innerTx.serializeForSigning()));
Expand All @@ -84,6 +85,7 @@ describe("test relayed v2 transaction builder", function () {
relayedTxV2.applySignature(await alice.signer.sign(relayedTxV2.serializeForSigning()));

assert.equal(relayedTxV2.getNonce().valueOf(), 37);
assert.equal(relayedTxV2.getVersion().valueOf(), 2);
assert.equal(
relayedTxV2.getData().toString(),
"relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@fc3ed87a51ee659f937c1a1ed11c1ae677e99629fae9cc289461f033e6514d1a8cfad1144ae9c1b70f28554d196bd6ba1604240c1c1dc19c959e96c1c3b62d0c");
Expand Down
2 changes: 2 additions & 0 deletions src/relayedTransactionV2Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export class RelayedTransactionV2Builder {
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()
});

if (this.relayerNonce) {
Expand Down
Loading

0 comments on commit 56ae79d

Please sign in to comment.