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

Add workflow for integration tests, fix encoding / decoding of managed decimals #479

Merged
Merged
65 changes: 65 additions & 0 deletions .github/workflows/test-localnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: MultiversX Integration Tests

on:
push:
branches:
- main
pull_request:
Copy link
Contributor

Choose a reason for hiding this comment

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

🚀


jobs:
integration_tests:
runs-on: ubuntu-latest

steps:
# Step 1: Checkout the repository
- name: Checkout code
uses: actions/checkout@v3

# Step 2: Set up Python environment
- name: Set up Python 3.x
uses: actions/setup-python@v4
with:
python-version: '3.x'

# Step 3: Install pipx (to manage Python tools)
- name: Install pipx
run: |
python3 -m pip install --user pipx
python3 -m pipx ensurepath
# Add the pipx binary location to PATH
echo "$HOME/.local/bin" >> $GITHUB_PATH
shell: bash

# Step 4: Install mxpy (MultiversX Python SDK)
- name: Install mxpy (MultiversX SDK)
run: |
pipx install multiversx-sdk-cli --force

# Step 5: Set up MultiversX localnet using mxpy
- name: Set up MultiversX localnet
run: |
# Start the local testnet with mxpy
mkdir -p ~/localnet && cd ~/localnet
mxpy localnet setup
nohup mxpy localnet start > localnet.log 2>&1 & echo $! > localnet.pid
sleep 60 # Allow time for the testnet to fully start

# Step 6: Install Node.js and dependencies
- name: Set up Node.js environment
uses: actions/setup-node@v3
with:
node-version: '16.x'

- name: Install Node.js dependencies
run: npm install

# Step 7: Run integration tests
- name: Run integration tests
run: |
npm run tests-localnet

# Step 8: Stop the testnet using the stored PID
- name: Stop MultiversX local testnet
if: success() || failure()
run: |
kill $(cat localnet.pid) || echo "Testnet already stopped"
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-core",
"version": "13.6.0",
"version": "13.6.1",
"description": "MultiversX SDK for JavaScript and TypeScript",
"author": "MultiversX",
"homepage": "https://multiversx.com",
Expand Down
13 changes: 13 additions & 0 deletions src/smartcontracts/codec/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
ArrayVec,
ManagedDecimalType,
ManagedDecimalValue,
ManagedDecimalSignedType,
ManagedDecimalSignedValue,
} from "../typesystem";
import { guardTrue } from "../../utils";
import { OptionValueBinaryCodec } from "./option";
Expand All @@ -28,6 +30,7 @@ import { EnumBinaryCodec } from "./enum";
import { TupleBinaryCodec } from "./tuple";
import { ArrayVecBinaryCodec } from "./arrayVec";
import { ManagedDecimalCodec } from "./managedDecimal";
import { ManagedDecimalSignedCodec } from "./managedDecimalSigned";

export class BinaryCodec {
readonly constraints: BinaryCodecConstraints;
Expand All @@ -39,6 +42,7 @@ export class BinaryCodec {
private readonly tupleCodec: TupleBinaryCodec;
private readonly enumCodec: EnumBinaryCodec;
private readonly managedDecimalCodec: ManagedDecimalCodec;
private readonly managedDecimalSignedCodec: ManagedDecimalSignedCodec;

constructor(constraints: BinaryCodecConstraints | null = null) {
this.constraints = constraints || new BinaryCodecConstraints();
Expand All @@ -50,6 +54,7 @@ export class BinaryCodec {
this.tupleCodec = new TupleBinaryCodec(this);
this.enumCodec = new EnumBinaryCodec(this);
this.managedDecimalCodec = new ManagedDecimalCodec(this);
this.managedDecimalSignedCodec = new ManagedDecimalSignedCodec(this);
}

decodeTopLevel<TResult extends TypedValue = TypedValue>(buffer: Buffer, type: Type): TResult {
Expand All @@ -64,6 +69,8 @@ export class BinaryCodec {
onTuple: () => this.tupleCodec.decodeTopLevel(buffer, <TupleType>type),
onEnum: () => this.enumCodec.decodeTopLevel(buffer, <EnumType>type),
onManagedDecimal: () => this.managedDecimalCodec.decodeTopLevel(buffer, <ManagedDecimalType>type),
onManagedDecimalSigned: () =>
this.managedDecimalSignedCodec.decodeTopLevel(buffer, <ManagedDecimalSignedType>type),
});

return <TResult>typedValue;
Expand All @@ -81,6 +88,8 @@ export class BinaryCodec {
onTuple: () => this.tupleCodec.decodeNested(buffer, <TupleType>type),
onEnum: () => this.enumCodec.decodeNested(buffer, <EnumType>type),
onManagedDecimal: () => this.managedDecimalCodec.decodeNested(buffer, <ManagedDecimalType>type),
onManagedDecimalSigned: () =>
this.managedDecimalSignedCodec.decodeNested(buffer, <ManagedDecimalSignedType>type),
});

return [<TResult>typedResult, decodedLength];
Expand All @@ -98,6 +107,8 @@ export class BinaryCodec {
onTuple: () => this.tupleCodec.encodeNested(<Tuple>typedValue),
onEnum: () => this.enumCodec.encodeNested(<EnumValue>typedValue),
onManagedDecimal: () => this.managedDecimalCodec.encodeNested(<ManagedDecimalValue>typedValue),
onManagedDecimalSigned: () =>
this.managedDecimalSignedCodec.encodeNested(<ManagedDecimalSignedValue>typedValue),
});
}

Expand All @@ -113,6 +124,8 @@ export class BinaryCodec {
onTuple: () => this.tupleCodec.encodeTopLevel(<Tuple>typedValue),
onEnum: () => this.enumCodec.encodeTopLevel(<EnumValue>typedValue),
onManagedDecimal: () => this.managedDecimalCodec.encodeTopLevel(<ManagedDecimalValue>typedValue),
onManagedDecimalSigned: () =>
this.managedDecimalSignedCodec.encodeTopLevel(<ManagedDecimalSignedValue>typedValue),
});
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/smartcontracts/codec/managedDecimal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BigNumber from "bignumber.js";
import { BigUIntValue, ManagedDecimalType, ManagedDecimalValue, U32Value } from "../typesystem";
import { BigUIntType, BigUIntValue, ManagedDecimalType, ManagedDecimalValue, U32Value } from "../typesystem";
import { BinaryCodec } from "./binary";
import { bufferToBigInt } from "./utils";
import { SizeOfU32 } from "./constants";
Expand Down Expand Up @@ -27,25 +27,25 @@ export class ManagedDecimalCodec {
if (type.isVariable()) {
const bigUintSize = buffer.length - SizeOfU32;

const value = new BigNumber(buffer.slice(0, bigUintSize).toString("hex"), 16);
const [value] = this.binaryCodec.decodeNested(buffer.slice(0, bigUintSize), new BigUIntType());
const scale = buffer.readUInt32BE(bigUintSize);

return new ManagedDecimalValue(value, scale);
return new ManagedDecimalValue(value.valueOf().shiftedBy(-scale), scale);
}

const value = bufferToBigInt(buffer);
const metadata = type.getMetadata();
const scale = typeof metadata === "number" ? metadata : 0;
return new ManagedDecimalValue(value, scale);
const scale = metadata !== "usize" ? parseInt(metadata.toString()) : 0;
return new ManagedDecimalValue(value.shiftedBy(-scale), scale);
}

encodeNested(value: ManagedDecimalValue): Buffer {
let buffers: Buffer[] = [];
const rawValue = new BigUIntValue(value.valueOf().shiftedBy(value.getScale()));
if (value.isVariable()) {
buffers.push(Buffer.from(this.binaryCodec.encodeNested(new BigUIntValue(value.valueOf()))));
buffers.push(Buffer.from(this.binaryCodec.encodeNested(rawValue)));
buffers.push(Buffer.from(this.binaryCodec.encodeNested(new U32Value(value.getScale()))));
} else {
buffers.push(Buffer.from(this.binaryCodec.encodeTopLevel(new BigUIntValue(value.valueOf()))));
buffers.push(this.binaryCodec.encodeTopLevel(rawValue));
}
return Buffer.concat(buffers);
}
Expand Down
57 changes: 57 additions & 0 deletions src/smartcontracts/codec/managedDecimalSigned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import BigNumber from "bignumber.js";
import { BigIntType, BigIntValue, ManagedDecimalSignedType, ManagedDecimalSignedValue, U32Value } from "../typesystem";
import { BinaryCodec } from "./binary";
import { bufferToBigInt } from "./utils";
import { SizeOfU32 } from "./constants";

export class ManagedDecimalSignedCodec {
private readonly binaryCodec: BinaryCodec;

constructor(binaryCodec: BinaryCodec) {
this.binaryCodec = binaryCodec;
}

decodeNested(buffer: Buffer, type: ManagedDecimalSignedType): [ManagedDecimalSignedValue, number] {
const length = buffer.readUInt32BE(0);
const payload = buffer.slice(0, length);

const result = this.decodeTopLevel(payload, type);
return [result, length];
}

decodeTopLevel(buffer: Buffer, type: ManagedDecimalSignedType): ManagedDecimalSignedValue {
if (buffer.length === 0) {
return new ManagedDecimalSignedValue(new BigNumber(0), 0);
}

if (type.isVariable()) {
const bigintSize = buffer.length - SizeOfU32;

const [value] = this.binaryCodec.decodeNested(buffer.slice(0, bigintSize), new BigIntType());
const scale = buffer.readUInt32BE(bigintSize);

return new ManagedDecimalSignedValue(value.valueOf().shiftedBy(-scale), scale);
}

const value = bufferToBigInt(buffer);
const metadata = type.getMetadata();
const scale = metadata !== "usize" ? parseInt(metadata.toString()) : 0;
return new ManagedDecimalSignedValue(value.shiftedBy(-scale), scale);
}

encodeNested(value: ManagedDecimalSignedValue): Buffer {
let buffers: Buffer[] = [];
const rawValue = new BigIntValue(value.valueOf().shiftedBy(value.getScale()));
if (value.isVariable()) {
buffers.push(Buffer.from(this.binaryCodec.encodeNested(rawValue)));
buffers.push(Buffer.from(this.binaryCodec.encodeNested(new U32Value(value.getScale()))));
} else {
buffers.push(Buffer.from(this.binaryCodec.encodeTopLevel(rawValue)));
}
return Buffer.concat(buffers);
}

encodeTopLevel(value: ManagedDecimalSignedValue): Buffer {
return this.encodeNested(value);
}
}
38 changes: 29 additions & 9 deletions src/smartcontracts/interaction.local.net.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ describe("test smart contract interactor", function () {
assert.isTrue(typedBundle.returnCode.equals(ReturnCode.Ok));
});

it("should interact with 'basic-features' (local testnet) using the SmartContractTransactionsFactory", async function () {
it("should interact with 'basic-features' (local testnet)", async function () {
this.timeout(140000);

let abiRegistry = await loadAbiRegistry("src/testdata/basic-features.abi.json");
Expand Down Expand Up @@ -226,7 +226,7 @@ describe("test smart contract interactor", function () {
.buildTransaction();

let additionInteraction = <Interaction>contract.methods
.managed_decimal_addition([new ManagedDecimalValue(2, 2), new ManagedDecimalValue(3, 2)])
.managed_decimal_addition([new ManagedDecimalValue("2.5", 2), new ManagedDecimalValue("2.7", 2)])
.withGasLimit(10000000)
.withChainID(network.ChainID)
.withSender(alice.address)
Expand All @@ -240,7 +240,7 @@ describe("test smart contract interactor", function () {

// log
let mdLnInteraction = <Interaction>contract.methods
.managed_decimal_ln([new ManagedDecimalValue(23000000000, 9)])
.managed_decimal_ln([new ManagedDecimalValue("23", 9)])
.withGasLimit(10000000)
.withChainID(network.ChainID)
.withSender(alice.address)
Expand All @@ -254,8 +254,8 @@ describe("test smart contract interactor", function () {

let additionVarInteraction = <Interaction>contract.methods
.managed_decimal_addition_var([
new ManagedDecimalValue(378298000000, 9, true),
new ManagedDecimalValue(378298000000, 9, true),
new ManagedDecimalValue("4", 2, true),
new ManagedDecimalValue("5", 2, true),
])
.withGasLimit(50000000)
.withChainID(network.ChainID)
Expand All @@ -268,33 +268,53 @@ describe("test smart contract interactor", function () {
.useThenIncrementNonceOf(alice.account)
.buildTransaction();

let lnVarInteraction = <Interaction>contract.methods
.managed_decimal_ln_var([new ManagedDecimalValue("23", 9, true)])
.withGasLimit(50000000)
.withChainID(network.ChainID)
.withSender(alice.address)
.withValue(0);

// managed_decimal_ln_var()
let lnVarTransaction = lnVarInteraction
.withSender(alice.address)
.useThenIncrementNonceOf(alice.account)
.buildTransaction();

// returnEgld()
await signTransaction({ transaction: returnEgldTransaction, wallet: alice });
let { bundle: bundleEgld } = await controller.execute(returnEgldInteraction, returnEgldTransaction);
assert.isTrue(bundleEgld.returnCode.equals(ReturnCode.Ok));
assert.lengthOf(bundleEgld.values, 1);
assert.deepEqual(bundleEgld.values[0], new ManagedDecimalValue(1, 18));
assert.deepEqual(bundleEgld.values[0], new ManagedDecimalValue("0.000000000000000001", 18));

// addition with const decimals()
await signTransaction({ transaction: additionTransaction, wallet: alice });
let { bundle: bundleAdditionConst } = await controller.execute(additionInteraction, additionTransaction);
assert.isTrue(bundleAdditionConst.returnCode.equals(ReturnCode.Ok));
assert.lengthOf(bundleAdditionConst.values, 1);
assert.deepEqual(bundleAdditionConst.values[0], new ManagedDecimalValue(5, 2));
assert.deepEqual(bundleAdditionConst.values[0], new ManagedDecimalValue("5.2", 2));

// log
await signTransaction({ transaction: mdLnTransaction, wallet: alice });
let { bundle: bundleMDLn } = await controller.execute(mdLnInteraction, mdLnTransaction);
assert.isTrue(bundleMDLn.returnCode.equals(ReturnCode.Ok));
assert.lengthOf(bundleMDLn.values, 1);
assert.deepEqual(bundleMDLn.values[0], new ManagedDecimalSignedValue(3135553845, 9));
assert.deepEqual(bundleMDLn.values[0], new ManagedDecimalSignedValue("3.135553845", 9));

// addition with var decimals
await signTransaction({ transaction: additionVarTransaction, wallet: alice });
let { bundle: bundleAddition } = await controller.execute(additionVarInteraction, additionVarTransaction);
assert.isTrue(bundleAddition.returnCode.equals(ReturnCode.Ok));
assert.lengthOf(bundleAddition.values, 1);
assert.deepEqual(bundleAddition.values[0], new ManagedDecimalValue(new BigNumber(6254154138880), 9));
assert.deepEqual(bundleAddition.values[0], new ManagedDecimalValue("9", 2));

// log
await signTransaction({ transaction: lnVarTransaction, wallet: alice });
let { bundle: bundleLnVar } = await controller.execute(lnVarInteraction, lnVarTransaction);
assert.isTrue(bundleLnVar.returnCode.equals(ReturnCode.Ok));
assert.lengthOf(bundleLnVar.values, 1);
assert.deepEqual(bundleLnVar.values[0], new ManagedDecimalSignedValue("3.135553845", 9));
});

it("should interact with 'counter' (local testnet)", async function () {
Expand Down
1 change: 1 addition & 0 deletions src/smartcontracts/typesystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export * from "./typeMapper";
export * from "./types";
export * from "./variadic";
export * from "./managedDecimal";
export * from "./managedDecimalSigned";
Loading
Loading