Skip to content

Commit

Permalink
feat: added support for atomic storage (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
sarvalabs-harshrastogi authored Apr 29, 2024
1 parent af590b1 commit 82dfb71
Show file tree
Hide file tree
Showing 139 changed files with 3,671 additions and 3,395 deletions.
70 changes: 67 additions & 3 deletions docs/source/logic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,76 @@ Variables
logic manifest. Developers can easily invoke and execute these routines, which
encapsulate specific functionalities and operations provided by the logic.

``ephemeralState`` - The ephemeral state, accessible via this variable,
represents the short-term or temporary state of the logic.

``persistentState`` - The persistent state is accessible via this variable. It
allows developers to retrieve state of the logic, which persists across
different invocations and interactions.

``ephemeralState`` - The ephemeral state, accessible via this variable,
represents the short-term or temporary state of the logic.
It contains the following method:

* ``get``
This method retrieves the value from persistent state using the storage key.
As the storage key hash generation is complex, a builder
object is passed to callback to generate storage key. The builder object has the following methods:

* ``entity`` - This method used to select the member of the state persistent.
* ``length`` - This method used to access length/size of `Array`, `Varray` and, `Map`.
* ``property`` - This method used to access the property of map using the passed key.
* ``at`` - This method used to access the element of `Array` and `Varray` using the passed index.
* ``field`` - This method used to access the field of `Class` using the passed field name.

.. code-block:: javascript
// Example
const logic = await getLogicDriver(logicId, wallet);
const symbol = await logic.persistentState.get(access => access.entity("symbol"));
console.log(symbol);
>> MOI
.. code-block:: javascript
// Example: if you want to access size of the array/map
const logic = await getLogicDriver(logicId, wallet);
const length = await logic.persistentState.get(access => access.entity("persons").length());
console.log(length);
>> 10
.. code-block:: javascript
// Example: if you want to access the balance of the address from the map
const logic = await getLogicDriver(logicId, wallet);
const address = "0x035dcdaa46f9b8984803b1105d8f327aef97de58481a5d3fea447735cee28fdc";
const balance = await logic.persistentState.get(access => access.entity("Balances").property(hexToBytes(address)));
console.log(balance);
>> 10000
.. code-block:: javascript
// Example: if you want to field of the class
const logic = await getLogicDriver(logicId, wallet);
const name = await logic.persistentState.get(access => access.entity("persons").field("name"));
console.log(name);
>> Alice
.. code-block:: javascript
// Example: if you want to access the element of the array
const logic = await getLogicDriver(logicId, wallet);
const product = await logic.persistentState.get(access => access.entity("Products").at(0));
console.log(name);
>> Chocolate
Functions
~~~~~~~~~
Expand Down Expand Up @@ -277,7 +341,7 @@ Usage
const logic = await getLogicDriver(logicId, wallet);
// Get the persistent state
const symbol = await logic.persistentState.get("symbol");
const symbol = await logic.persistentState.get(access => access.entity("symbol"));
console.log(symbol); // MOI
Expand Down
12 changes: 10 additions & 2 deletions docs/source/manifest-call-encoder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,21 @@ Methods
.. code-block:: javascript
// Example
const error = "0x0e4f0666ae03737472696e67536f6d657468696e672077656e742077726f6e673f06b60166756e6374696f6e31282966756e6374696f6e322829";
const error = "0x0e6f0666d104de04737472696e67696e73756666696369656e742062616c616e636520666f722073656e6465723f06e60172756e74696d652e726f6f742829726f7574696e652e5472616e736665722829205b3078635d202e2e2e205b307831623a205448524f57203078355d";
const exception = ManifestCoder.decodeException(error);
console.log(exception)
>> { class: 'string', data: 'Something went wrong', trace: [ 'function1()', 'function2()' ] }
>> {
class: "string",
error: "insufficient balance for sender",
revert: false,
trace: [
"runtime.root()",
"routine.Transfer() [0xc] ... [0x1b: THROW 0x5]"
],
}
.. autofunction:: decodeState

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@
"@types/aes-js": "^3.1.4",
"@types/jest": "^29.4.0",
"jest": "^29.4.3",
"ts-node": "10.9.2",
"ts-jest": "^29.0.5"
"jsdoc": "^4.0.2",
"ts-jest": "^29.0.5",
"ts-node": "10.9.2"
},
"dependencies": {
"@noble/hashes": "^1.1.5",
Expand Down
133 changes: 64 additions & 69 deletions packages/js-moi-logic/__tests__/logic.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { JsonRpcProvider } from "js-moi-providers";
import { Wallet } from "js-moi-wallet";

import { LogicDriver, getLogicDriver } from "../src.ts/logic-driver";
import { LogicFactory } from "../src.ts/logic-factory";

import manifest from "../manifests/erc20.json";
import type { LogicManifest } from "js-moi-manifest";
import { loadManifestFromFile } from "js-moi-manifest/__tests__/utils/helper";
import { JsonRpcProvider, type InteractionReceipt, type InteractionResponse } from "js-moi-providers";

const HOST = "http://localhost:1600/";
const MNEMONIC = "visa security tobacco hood forget rate exhibit habit deny good sister slender";
const MNEMONIC = "laptop hybrid ripple unaware entire cover flag rally deliver adjust nerve ready";
const INITIAL_SUPPLY = 100000000;
const SYMBOL = "MOI";
const RECEIVER = "0x4cdc9a1430ca00cbaaab5dcd858236ba75e64b863d69fa799d31854e103ddf72";
Expand All @@ -28,33 +29,35 @@ it("should initialize the wallet", async () => {

describe("Logic", () => {
let logicId: string | undefined;
let manifest: LogicManifest.Manifest;

describe("deploy logic", () => {
it("should deploy logic without options", async () => {
const factory = new LogicFactory(manifest, wallet);
let ix: InteractionResponse;
let receipt: InteractionReceipt;

const symbol = SYMBOL;
const supply = INITIAL_SUPPLY;
beforeAll(async () => {
manifest = await loadManifestFromFile("../../manifests/tokenledger.json");

const ix = await factory.deploy("Seed!", symbol, supply);

const receipt = await ix.wait();
const result = await ix.result();
logicId = result.logic_id;
const factory = new LogicFactory(manifest, wallet);
ix = await factory.deploy("Seeder", SYMBOL, INITIAL_SUPPLY);
receipt = await ix.wait();
const result = await ix.result();

logicId = result.logic_id;
});

describe("deploy logic", () => {
it("should deploy logic without options", async () => {
expect(ix.hash).toBeDefined();
expect(receipt).toBeDefined();

});


it("should deploy logic with options", async () => {
const factory = new LogicFactory(manifest, wallet);
const symbol = "MOI";
const supply = 100000000;
const option = { fuelPrice: 1, fuelLimit: 3000 + Math.floor(Math.random() * 3000) }
const ix = await factory.deploy("Seed!", symbol, supply, option);
const option = { fuelPrice: 1, fuelLimit: 3000 + Math.floor(Math.random() * 3000) };
const ix = await factory.deploy("Seeder", symbol, supply, option);

const receipt = await ix.wait();
const result = await ix.result();
logicId = result.logic_id;
Expand All @@ -68,59 +71,43 @@ describe("Logic", () => {
let logic: LogicDriver;

beforeAll(async () => {
if(logicId == null) {
if (logicId == null) {
expect(logicId).toBeDefined();
return;
};
}

logic = await getLogicDriver(logicId, wallet);
logic = await getLogicDriver(logicId!, wallet);
});

it("should able to retrieve balance of the account", async () => {
const { balance } = await logic.routines.BalanceOf(wallet.getAddress());

expect(balance).toBe(INITIAL_SUPPLY);
});

it("should return object when multiple values are returned", async () => {
const values = await logic.routines.DoubleReturnValue(wallet.getAddress());
const { symbol, supply } = values;

expect(values).toBeDefined();
expect(typeof symbol).toBe('string');
expect(typeof supply).toBe('number');
expect(balance).toBe(INITIAL_SUPPLY);
});

it("should able to transfer without option", async () => {
const amount = Math.floor(Math.random() * 1000);
const ix = await logic.routines.Transfer(RECEIVER, amount);
const receipt = await ix.wait();
const { success } = await ix.result();

expect(ix.hash).toBeDefined();
expect(receipt).toBeDefined();
expect(typeof success).toBe('boolean');
});

it("should able to transfer with option", async () => {
const amount = Math.floor(Math.random() * 1000);
const option = { fuelPrice: 1, fuelLimit: 1000 + Math.floor(Math.random() * 1000) }
const option = { fuelPrice: 1, fuelLimit: 1000 + Math.floor(Math.random() * 1000) };
const ix = await logic.routines.Transfer(RECEIVER, amount, option);
const receipt = await ix.wait();
const result = await ix.result();
const { balance } = await logic.routines.BalanceOf(RECEIVER);

const { success } = result;

expect(balance).toBeGreaterThanOrEqual(amount);
expect(ix.hash).toBeDefined();
expect(receipt).toBeDefined();
expect(typeof success).toBe('boolean');
});

it("should throw error when logic execution throw error using `result()`", async () => {
const { balance } = await logic.routines.BalanceOf(wallet.getAddress());
const amount = balance + 1
const amount = balance + 1;
const ix = await logic.routines.Transfer(RECEIVER, amount);

try {
Expand All @@ -133,7 +120,7 @@ describe("Logic", () => {

it("should throw error when logic execution throw error using `wait()`", async () => {
const { balance } = await logic.routines.BalanceOf(wallet.getAddress());
const amount = balance + 1
const amount = balance + 1;
const ix = await logic.routines.Transfer(RECEIVER, amount);

try {
Expand All @@ -145,69 +132,77 @@ describe("Logic", () => {
});

it("should be able to read from persistent storage", async () => {
const symbol = await logic.persistentState.get("symbol");
const symbol = await logic.persistentState.get((b) => b.entity("symbol"));

expect(symbol).toBe(SYMBOL);
});

it("should throw error when reading from persistent storage with invalid key", async () => {
const invalidKey = "invalid-key";

expect(async () => {
await logic.persistentState.get(invalidKey);
}).rejects.toThrow(`The provided slot "${invalidKey}" does not exist.`);
await logic.persistentState.get((b) => b.entity(invalidKey));
}).rejects.toThrow(`'${invalidKey}' is not member of persistent state`);
});
});

describe("logic driver initialized using provider", () => {
let logic: LogicDriver;

beforeAll(async () => {
if(logicId == null) {
if (logicId == null) {
expect(logicId).toBeDefined();
return;
};
}

logic = await getLogicDriver(logicId, PROVIDER);
logic = await getLogicDriver(logicId!, PROVIDER);
});

it("should able to retrieve balance of the account", async () => {
const { balance } = await logic.routines.BalanceOf(wallet.getAddress());

expect(balance).toBeGreaterThan(0);
});

it("should return object when multiple values are returned", async () => {
const values = await logic.routines.DoubleReturnValue(wallet.getAddress());

expect(values).toBeDefined();

const { symbol, supply } = values;

expect(typeof symbol).toBe('string');
expect(typeof supply).toBe('number');
expect(balance).toBeGreaterThan(0);
});

it("should throw an exception in mutating routine call", async () => {
const amount = Math.floor(Math.random() * 1000);

expect(async () => {
await logic.routines.Transfer(RECEIVER, amount);
}).rejects.toThrow("Mutating routine calls require a signer to be initialized.");
});

it("should be able to read from persistent storage", async () => {
const symbol = await logic.persistentState.get("symbol");
const symbol = await logic.persistentState.get((b) => b.entity("symbol"));

expect(symbol).toBe(SYMBOL);
});

it("should throw an exception when reading from persistent storage with invalid key", async () => {
const invalidKey = "invalid-key";

expect(async () => {
await logic.persistentState.get(invalidKey);
}).rejects.toThrow(`The provided slot "${invalidKey}" does not exist.`);
await logic.persistentState.get((b) => b.entity(invalidKey));
}).rejects.toThrow(`'${invalidKey}' is not member of persistent state`);
});
});
})

let logic: LogicDriver;

beforeAll(async () => {
logic = new LogicDriver("0x", manifest, wallet);
});

it("should be able return routine is mutable or not", () => {
const routine = [
{ name: "Transfer", mutable: true },
{ name: "BalanceOf", mutable: false },
];

for (const { name, mutable } of routine) {
if (name in logic.routines) {
const isMutable = logic.routines[name].isMutable();
expect(isMutable).toBe(mutable);
}
}
});
});
Loading

0 comments on commit 82dfb71

Please sign in to comment.