Skip to content

Commit

Permalink
Merge pull request #253 from near/ft-example-improvement
Browse files Browse the repository at this point in the history
Add Storage Management to FT Example
  • Loading branch information
volovyks authored Oct 13, 2022
2 parents 8f676ae + 0adb4cc commit 56d31cc
Show file tree
Hide file tree
Showing 6 changed files with 451 additions and 147 deletions.
215 changes: 175 additions & 40 deletions examples/__tests__/test-fungible-token.ava.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,187 @@
import { Worker } from "near-workspaces";
import { Worker, NEAR } from "near-workspaces";
import test from "ava";

test.beforeEach(async (t) => {
// Init the worker and start a Sandbox server
const worker = await Worker.init();

// Prepare sandbox for tests, create accounts, deploy contracts, etx.
const root = worker.rootAccount;
const totalSupply = 1000;
const yoctoAccountStorage = "90";

// Deploy the ft contract.
const ft = await root.devDeploy("./build/fungible-token.wasm");
const root = worker.rootAccount;
const xcc = await root.devDeploy("./build/fungible-token-helper.wasm");
const ft = await root.createSubAccount("ft");
await ft.deploy("./build/fungible-token.wasm");
await root.call(ft, "init", {
owner_id: root.accountId,
total_supply: totalSupply.toString(),
});
const alice = await root.createSubAccount("alice", {
initialBalance: NEAR.parse("10 N").toJSON(),
});

// Init the contracts
await ft.call(ft, "init", { prefix: "a", totalSupply: "1000" });

// Create test accounts
const ali = await root.createSubAccount("ali");
const bob = await root.createSubAccount("bob");

// Save state for test runs, it is unique for each test
t.context.worker = worker;
t.context.accounts = { root, ft, ali, bob, xcc };
t.context.accounts = { root, ft, alice, xcc };
t.context.variables = { totalSupply, yoctoAccountStorage };
});

test.afterEach.always(async (t) => {
await t.context.worker.tearDown().catch((error) => {
console.log("Failed tear down the worker:", error);
console.log("Failed to tear down the worker:", error);
});
});

test("should register account and pay for storage", async (t) => {
const { ft, alice } = t.context.accounts;
const { yoctoAccountStorage } = t.context.variables;
const result = await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
const aliceAfterBalance = await alice.balance();
const expected = {
message: `Account ${alice.accountId} registered with storage deposit of ${yoctoAccountStorage}`,
};
t.deepEqual(result, expected);
t.true(
aliceAfterBalance.total > NEAR.parse("9 N").toJSON(),
"alice should have received a refund"
);
});

test("should return message when account is already registered and not refund when no deposit is attached", async (t) => {
const { ft, alice } = t.context.accounts;
const { yoctoAccountStorage } = t.context.variables;
const result = await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
const expected = {
message: `Account ${alice.accountId} registered with storage deposit of ${yoctoAccountStorage}`,
};
t.deepEqual(result, expected);
const result2 = await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("0 N").toJSON() }
);
t.is(result2.message, "Account is already registered");
});

test("should return message and refund predecessor caller when trying to pay for storage for an account that is already registered", async (t) => {
const { ft, alice } = t.context.accounts;
const { yoctoAccountStorage } = t.context.variables;
const result = await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
const expected = {
message: `Account ${alice.accountId} registered with storage deposit of ${yoctoAccountStorage}`,
};
t.deepEqual(result, expected);
const result2 = await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
t.is(
result2.message,
"Account is already registered, deposit refunded to predecessor"
);
const aliceBalance = await alice.balance();
t.is(
aliceBalance.total > NEAR.parse("9 N"),
true,
"alice should have received a refund"
);
});

test("should return message when trying to pay for storage with less than the required amount and refund predecessor caller", async (t) => {
const { ft, alice } = t.context.accounts;
const { yoctoAccountStorage } = t.context.variables;
const result = await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.from("40").toJSON() }
);
t.is(
result.message,
`Not enough attached deposit to cover storage cost. Required: ${yoctoAccountStorage}`
);
});

test("should throw when trying to transfer for an unregistered account", async (t) => {
const { ft, alice, root } = t.context.accounts;
try {
await root.call(
ft,
"ft_transfer",
{ receiver_id: alice.accountId, amount: "1" },
{ attachedDeposit: NEAR.from("1").toJSON() }
);
} catch (error) {
t.true(
error.message.includes(`Account ${alice.accountId} is not registered`)
);
}
});

test("Owner has all balance in the beginning", async (t) => {
const { ft } = t.context.accounts;
const result = await ft.view("ftBalanceOf", { accountId: ft.accountId });
const { ft, root } = t.context.accounts;
const result = await ft.view("ft_balance_of", { account_id: root.accountId });
t.is(result, "1000");
});

test("Can transfer if balance is sufficient", async (t) => {
const { ali, ft } = t.context.accounts;

await ft.call(ft, "ftTransfer", { receiverId: ali.accountId, amount: "100" });
const aliBalance = await ft.view("ftBalanceOf", { accountId: ali.accountId });
const { alice, ft, root } = t.context.accounts;
await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
await root.call(
ft,
"ft_transfer",
{ receiver_id: alice.accountId, amount: "100" },
{ attachedDeposit: NEAR.from("1").toJSON() }
);
const aliBalance = await ft.view("ft_balance_of", {
account_id: alice.accountId,
});
t.is(aliBalance, "100");
const ownerBalance = await ft.view("ftBalanceOf", {
accountId: ft.accountId,
const ownerBalance = await ft.view("ft_balance_of", {
account_id: root.accountId,
});
t.is(ownerBalance, "900");
});

test("Cannot transfer if balance is not sufficient", async (t) => {
const { ali, bob, ft } = t.context.accounts;
const { alice, root, ft } = t.context.accounts;
await alice.call(
ft,
"storage_deposit",
{ account_id: alice.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
try {
await ali.call(ft, "ftTransfer", {
receiverId: bob.accountId,
amount: "100",
});
await alice.call(
ft,
"ft_transfer",
{
receiverId: root.accountId,
amount: "100",
},
{ attachedDeposit: NEAR.from("1").toJSON() }
);
} catch (e) {
t.assert(
e
Expand All @@ -67,22 +194,30 @@ test("Cannot transfer if balance is not sufficient", async (t) => {
});

test("Cross contract transfer", async (t) => {
const { xcc, ft } = t.context.accounts;
await ft.call(
const { xcc, ft, root } = t.context.accounts;
await xcc.call(
ft,
"ftTransferCall",
{ receiverId: xcc.accountId, amount: "900", memo: null, msg: "test msg" },
{ gas: 200000000000000 }
"storage_deposit",
{ account_id: xcc.accountId },
{ attachedDeposit: NEAR.parse("1 N").toJSON() }
);
const aliBalance = await ft.view("ftBalanceOf", { accountId: xcc.accountId });
t.is(aliBalance, "900");
const aliSubContractData = await xcc.view("getContractData");
await root.call(
ft,
"ft_transfer_call",
{ receiver_id: xcc.accountId, amount: "900", memo: null, msg: "test msg" },
{ gas: 200000000000000, attachedDeposit: NEAR.from("1").toJSON() }
);
const xccBalance = await ft.view("ft_balance_of", {
account_id: xcc.accountId,
});
t.is(xccBalance, "900");
const aliSubContractData = await xcc.view("get_contract_data");
t.is(
aliSubContractData,
`[900 from ${ft.accountId} to ${xcc.accountId}] test msg `
`[900 from ${root.accountId} to ${xcc.accountId}] test msg `
);
const ownerBalance = await ft.view("ftBalanceOf", {
accountId: ft.accountId,
const ownerBalance = await ft.view("ft_balance_of", {
account_id: root.accountId,
});
t.is(ownerBalance, "100");
});
2 changes: 1 addition & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"build:counter-ts": "near-sdk-js build src/counter.ts build/counter-ts.wasm",
"build:cross-contract-call": "near-sdk-js build src/status-message.js build/status-message.wasm && near-sdk-js build src/cross-contract-call.js build/cross-contract-call.wasm",
"build:fungible-token-lockable": "near-sdk-js build src/fungible-token-lockable.js build/fungible-token-lockable.wasm",
"build:fungible-token": "near-sdk-js build src/fungible-token.js build/fungible-token.wasm && near-sdk-js build src/fungible-token-helper.js build/fungible-token-helper.wasm",
"build:fungible-token": "near-sdk-js build src/fungible-token.ts build/fungible-token.wasm && near-sdk-js build src/fungible-token-helper.ts build/fungible-token-helper.wasm",
"build:non-fungible-token": "near-sdk-js build src/non-fungible-token-receiver.js build/non-fungible-token-receiver.wasm && near-sdk-js build src/non-fungible-token.js build/non-fungible-token.wasm",
"build:status-message-collections": "near-sdk-js build src/status-message-collections.js build/status-message-collections.wasm",
"build:parking-lot": "near-sdk-js build src/parking-lot.ts build/parking-lot.wasm",
Expand Down
19 changes: 0 additions & 19 deletions examples/src/fungible-token-helper.js

This file was deleted.

27 changes: 27 additions & 0 deletions examples/src/fungible-token-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NearBindgen, call, view } from "near-sdk-js";

@NearBindgen({})
class _FungibleTokenHelper {
data = "";

@call({})
ft_on_transfer({
sender_id,
amount,
msg,
receiver_id,
}: {
sender_id: string;
amount: string;
msg: string;
receiver_id: string;
}) {
const concatString = `[${amount} from ${sender_id} to ${receiver_id}] ${msg} `;
this.data = this.data.concat("", concatString);
}

@view({})
get_contract_data() {
return this.data;
}
}
Loading

0 comments on commit 56d31cc

Please sign in to comment.