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 currency abstraction #2707

Merged
merged 15 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions packages/grid_client/scripts/tft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { CurrencyModel } from "../src";
import { getClient } from "./client_loader";
import { log } from "./utils";

async function convertTFTtoUSD(client, amount) {
const res = await client.currency.convertTFTtoUSD(amount);
log("================= Convert TFT =================");
log(res);
log("================= Convert TFT =================");
}

async function convertUSDtoTFT(client, amount) {
const res = await client.currency.convertUSDtoTFT(amount);
log("================= Convert USD =================");
log(res);
log("================= Convert USD =================");
}

async function dailyTFT(client, hourlyTFT) {
const res = await client.currency.dailyTFT(hourlyTFT);
log("================= Daily TFT =================");
log(res);
log("================= Daily TFT =================");
}

async function monthlyTFT(client, hourlyTFT) {
const res = await client.currency.monthlyTFT(hourlyTFT);
log("================= Monthly TFT =================");
log(res);
log("================= Monthly TFT =================");
}

async function yearlyTFT(client, hourlyTFT) {
const res = await client.currency.yearlyTFT(hourlyTFT);
log("================= Yearly TFT =================");
log(res);
log("================= Yearly TFT =================");
}

async function dailyUSD(client, hourlyUSD) {
const res = await client.currency.dailyUSD(hourlyUSD);
log("================= Daily USD =================");
log(res);
log("================= Daily USD =================");
}

async function monthlyUSD(client, hourlyUSD) {
const res = await client.currency.monthlyUSD(hourlyUSD);
log("================= Monthly USD =================");
log(res);
log("================= Monthly USD =================");
}

async function yearlyUSD(client, hourlyUSD) {
const res = await client.currency.yearlyUSD(hourlyUSD);
log("================= Yearly USD =================");
log(res);
log("================= Yearly USD =================");
}
async function main() {
const grid = await getClient();

const amount: CurrencyModel = {
amount: 1,
};

await convertTFTtoUSD(grid, amount);
await convertUSDtoTFT(grid, amount);
await dailyTFT(grid, amount);
await monthlyTFT(grid, amount);
await yearlyTFT(grid, amount);
await dailyUSD(grid, amount);
await monthlyUSD(grid, amount);
await yearlyUSD(grid, amount);

await grid.disconnect();
}

main();
3 changes: 2 additions & 1 deletion packages/grid_client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { isExposed } from "./helpers/expose";
import { formatErrorMessage, generateString } from "./helpers/utils";
import * as modules from "./modules/index";
import { appPath } from "./storage/backend";
import { BackendStorage, BackendStorageType } from "./storage/backend";
import { BackendStorageType } from "./storage/backend";
import { KeypairType } from "./zos/deployment";

class GridClient {
Expand Down Expand Up @@ -41,6 +41,7 @@ class GridClient {
stellar: modules.stellar;
blockchain: modules.blockchain;
calculator: modules.calculator;
currency: modules.currency;
utility: modules.utility;
farmerbot: modules.farmerbot;
farms: modules.farms;
Expand Down
12 changes: 6 additions & 6 deletions packages/grid_client/src/helpers/validator.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ValidationError } from "@threefold/types";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { validateSync } from "class-validator";

async function validateObject(obj) {
const errors = await validate(obj);
function validateObject(obj) {
const errors = validateSync(obj);
// errors is an array of validation errors
if (errors.length > 0) {
console.log("Validation failed. errors:", errors);
Expand All @@ -13,13 +13,13 @@ async function validateObject(obj) {
// used as decorator
function validateInput(target, propertyKey: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = async function (...args) {
descriptor.value = function (...args) {
const types = Reflect.getMetadata("design:paramtypes", target, propertyKey);
for (let i = 0; i < args.length; i++) {
const input = plainToInstance(types[i], args[i], { excludeExtraneousValues: true });
await validateObject(input);
validateObject(input);
}
return await method.apply(this, args);
return method.apply(this, args);
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/grid_client/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export * from "./farmerbot";
export * from "./farms";
export * from "./networks";
export * from "./bridge";
export * from "./tft";
export * from "./base";
5 changes: 5 additions & 0 deletions packages/grid_client/src/modules/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,10 @@ class GetActiveContractsModel {
@Expose() @IsInt() @IsNotEmpty() @Min(1) nodeId: number;
}

class CurrencyModel {
@Expose() @IsNumber() @IsNotEmpty() @Min(0) amount: number; // hourly amount
}

interface GPUCardInfo {
id: string;
contract: number;
Expand Down Expand Up @@ -977,4 +981,5 @@ export {
NodeCPUTest,
NodeIPValidation,
NodeIPerf,
CurrencyModel,
};
82 changes: 82 additions & 0 deletions packages/grid_client/src/modules/tft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Decimal from "decimal.js";

import { GridClientConfig, TFClient } from "..";
import { expose, validateInput } from "../helpers";
import { CurrencyModel } from "./models";

class TFTUSDConversionService {
private tfclient: TFClient;
private rate: number;

constructor(public config: GridClientConfig, public decimals = 2) {
this.decimals = decimals;
zaelgohary marked this conversation as resolved.
Show resolved Hide resolved
this.tfclient = config.tfclient;
this.tfclient.tftPrice.get().then(res => {
xmonader marked this conversation as resolved.
Show resolved Hide resolved
this.rate = res;
});
}

@expose
@validateInput
normalizeCurrency(options: CurrencyModel): string {
return new Decimal(options.amount).toFixed(this.decimals);
}

@expose
@validateInput
convertUSDtoTFT(options: CurrencyModel): string {
const amount = options.amount / this.rate;
return this.normalizeCurrency({ amount });
}

@expose
@validateInput
convertTFTtoUSD(options: CurrencyModel): string {
const amount = options.amount * this.rate;
return this.normalizeCurrency({ amount });
}

zaelgohary marked this conversation as resolved.
Show resolved Hide resolved
@expose
@validateInput
dailyTFT(options: CurrencyModel): string {
const hours = options.amount * 24;
return this.normalizeCurrency({ amount: hours });
}

@expose
@validateInput
monthlyTFT(options: CurrencyModel): string {
const months = +this.dailyTFT(options) * 30;
return this.normalizeCurrency({ amount: months });
}

@expose
@validateInput
yearlyTFT(options: CurrencyModel): string {
const years = +this.monthlyTFT(options) * 12;
return this.normalizeCurrency({ amount: years });
}

@expose
@validateInput
dailyUSD(options: CurrencyModel): string {
const hours = options.amount * 24;
return this.normalizeCurrency({ amount: hours });
}

@expose
@validateInput
monthlyUSD(options: CurrencyModel): string {
const months = +this.dailyUSD(options) * 30;
return this.normalizeCurrency({ amount: months });
}

@expose
@validateInput
yearlyUSD(options: CurrencyModel): string {
const years = +this.monthlyUSD(options) * 12;
return this.normalizeCurrency({ amount: years });
}
}

export { TFTUSDConversionService as currency };
162 changes: 162 additions & 0 deletions packages/grid_client/tests/modules/tft.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { ValidationError } from "@threefold/types";
import Decimal from "decimal.js";

import { currency, type GridClient } from "../../src";
import { getClient } from "../client_loader";

jest.setTimeout(300000);
let grid: GridClient;
let tftPrice: number;

beforeAll(async () => {
grid = await getClient();
tftPrice = await grid.tfclient.tftPrice.get();
grid.currency.decimals = 5;
});

afterAll(async () => {
await grid.disconnect();
});

describe("Testing TFT module", () => {
test("tft module to be instance of TFTUSDConversionService", async () => {
expect(await grid.currency).toBeInstanceOf(currency);
});

test("should return value with 2 decimals.", async () => {
const result = await grid.currency.normalizeCurrency({ amount: 1 });

expect(typeof result).toBe("string");
expect(result).toBe(new Decimal(1).toFixed(grid.currency.decimals));
});

test("should convert to the correct value based on tftPrice.", async () => {
const hourlyTFT = { amount: 1 };
const result = await grid.currency.convertTFTtoUSD(hourlyTFT);

expect(typeof result).toBe("string");
expect(result).toBe(new Decimal(1 * tftPrice).toFixed(grid.currency.decimals));
});

test("convertTFTtoUSD function to throw if passed a negative value.", async () => {
const result = async () => await grid.currency.convertTFTtoUSD({ amount: -1 });

await expect(result).rejects.toThrow();
await expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("convertUSDtoTFT function returns a valid value.", async () => {
const usd = 1;
const result = await grid.currency.convertUSDtoTFT({ amount: usd });

expect(typeof result).toBe("string");
expect(result).toEqual(new Decimal(1 / tftPrice).toFixed(grid.currency.decimals));
});

test("convertUSDtoTFT function to throw if passed a negative value.", async () => {
const result = async () => await grid.currency.convertUSDtoTFT({ amount: -1 });

await expect(result).rejects.toThrow();
await expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("dailyTFT function returns a valid value.", async () => {
const tfts = 1;
const result = await grid.currency.dailyTFT({ amount: tfts });
const expected_result = new Decimal(tfts * 24).toFixed(grid.currency.decimals);

expect(typeof result).toBe("string");
expect(result).toBe(expected_result);
});

test("dailyTFT function throws if passed anything other than a positive value.", async () => {
const result = async () => await grid.currency.dailyTFT({ amount: -1 });

expect(result).rejects.toThrow();
expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("monthlyTFT function returns a valid value.", async () => {
const tfts = 1;
const result = await grid.currency.monthlyTFT({ amount: tfts });
const expected_result = new Decimal(tfts * 24 * 30).toFixed(grid.currency.decimals);

expect(typeof result).toBe("string");
expect(result).toBe(expected_result);
});

test("monthlyTFT function throws if passed anything other than a positive value.", async () => {
const result = async () => await grid.currency.monthlyTFT({ amount: -1 });

await expect(result).rejects.toThrow();
await expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("yearlyTFT function returns a valid value.", async () => {
const tfts = 1;
const result = await grid.currency.yearlyTFT({ amount: tfts });
const expected_result = new Decimal(+(await grid.currency.monthlyTFT({ amount: tfts })) * 12).toFixed(
grid.currency.decimals,
);

expect(typeof result).toBe("string");
expect(result).toBe(expected_result);
});

test("yearlyTFT function throws if passed anything other than a positive value.", async () => {
const result = async () => grid.currency.yearlyTFT({ amount: -1 });

await expect(result).rejects.toThrow();
await expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("dailyUSD function returns a valid value.", async () => {
const tfts = 1;
const result = await grid.currency.dailyUSD({ amount: tfts });
const expected_result = new Decimal(tfts * 24).toFixed(grid.currency.decimals);

expect(typeof result).toBe("string");
expect(result).toBe(expected_result);
});

test("dailyUSD function throws if passed anything other than a positive value.", async () => {
const result = async () => await grid.currency.dailyUSD({ amount: -1 });

expect(result).rejects.toThrow();
expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("monthlyUSD function returns a valid value.", async () => {
const tfts = 1;
const result = await grid.currency.monthlyUSD({ amount: tfts });
const expected_result = new Decimal(tfts * 24 * 30).toFixed(grid.currency.decimals);

expect(typeof result).toBe("string");
expect(result).toBe(expected_result);
});

test("monthlyUSD function throws if passed anything other than a positive value.", async () => {
const result = async () => await grid.currency.monthlyUSD({ amount: -1 });

await expect(result).rejects.toThrow();
await expect(result).rejects.toBeInstanceOf(ValidationError);
});

test("yearlyUSD function returns a valid value.", async () => {
const tfts = 1;
const result = await grid.currency.yearlyUSD({ amount: tfts });
const expected_result = new Decimal(+(await grid.currency.monthlyUSD({ amount: tfts })) * 12).toFixed(
grid.currency.decimals,
);

expect(typeof result).toBe("string");
expect(result).toBe(expected_result);
});

test("yearlyUSD function throws if passed anything other than a positive value.", async () => {
const result = async () => grid.currency.yearlyUSD({ amount: -1 });

await expect(result).rejects.toThrow();
await expect(result).rejects.toBeInstanceOf(ValidationError);
});
});
Loading