Skip to content

Commit

Permalink
Merge pull request #44 from near/daniyar/lockable-ft-example
Browse files Browse the repository at this point in the history
Lockable fungible token example
  • Loading branch information
ailisp authored May 24, 2022
2 parents eee284c + 5c11143 commit 182ee66
Show file tree
Hide file tree
Showing 9 changed files with 7,397 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/lockable-fungible-token/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
node_modules
16 changes: 16 additions & 0 deletions examples/lockable-fungible-token/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Lockable fungible token contract in JavaScript

This is an equivalent JavaScript implementation of the lockable fungible token example.

## Build the contract

First ensure JSVM contract is build and deployed locally, follow [Local Installation](https://github.com/near/near-sdk-js#local-installation). Then run:
```
npm i
npm run build
```

## Test the contract with workspaces-js
```
npm run test
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { Worker } from 'near-workspaces';
import { readFile } from 'fs/promises'
import test from 'ava';

function encodeCall(contract, method, args) {
return Buffer.concat([Buffer.from(contract), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(JSON.stringify(args))])
}

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;

// Deploy the jsvm contract.
const jsvm = await root.createAndDeploy(
root.getSubAccount('jsvm').accountId,
'./node_modules/near-sdk-js/res/jsvm.wasm',
);

// Deploy lockable FT JS contract
const lockableFt = await root.createSubAccount('lockable-ft');
let contract_base64 = (await readFile('build/contract.base64')).toString();
await lockableFt.call(jsvm, 'deploy_js_contract', Buffer.from(contract_base64, 'base64'), { attachedDeposit: '1000000000000000000000000' });
await lockableFt.call(jsvm, 'call_js_contract', encodeCall(lockableFt.accountId, 'init', ['prefix', 10000]), { attachedDeposit: '1000000000000000000000000' });

// Test users
const ali = await root.createSubAccount('ali');
const bob = await root.createSubAccount('bob');

// Save state for test runs
t.context.worker = worker;
t.context.accounts = { root, jsvm, lockableFt, ali, bob };
});

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

test('Owner initial details', async t => {
const { jsvm, lockableFt } = t.context.accounts;
const totalSupply = await jsvm.view('view_js_contract', encodeCall(lockableFt.accountId, 'getTotalSupply', []));
t.is(totalSupply, 10000);
const totalBalance = await jsvm.view('view_js_contract', encodeCall(lockableFt.accountId, 'getTotalBalance', [lockableFt.accountId]));
t.is(totalBalance, 10000);
const unlockedBalance = await jsvm.view('view_js_contract', encodeCall(lockableFt.accountId, 'getUnlockedBalance', [lockableFt.accountId]));
t.is(unlockedBalance, 10000);
const allowance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getAllowance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(allowance, 0);
const lockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getLockedBalance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(lockedBalance, 0);
});

test('Set allowance', async t => {
const { jsvm, lockableFt, ali } = t.context.accounts;
await lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'setAllowance', [ali.accountId, 100]),
{ attachedDeposit: '100000000000000000000000' }
);
const aliAllowance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getAllowance', [lockableFt.accountId, ali.accountId])
);
t.is(aliAllowance, 100);
const contractAllowance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getAllowance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(contractAllowance, 0);
});

test('Fail to set allowance for oneself', async t => {
const { jsvm, lockableFt } = t.context.accounts;
const error = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'setAllowance', [lockableFt.accountId, 100]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error.message.includes(`Can't set allowance for yourself`));
});

test('Lock owner', async t => {
const { jsvm, lockableFt, ali } = t.context.accounts;
await lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'lock', [lockableFt.accountId, 100]),
{ attachedDeposit: '100000000000000000000000' }
);
const unlockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getUnlockedBalance', [lockableFt.accountId])
);
t.is(unlockedBalance, 9900);
const allowance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getAllowance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(allowance, 0);
const lockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getLockedBalance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(lockedBalance, 100);
});

test('Lock failures', async t => {
const { jsvm, lockableFt, ali } = t.context.accounts;
const error1 = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'lock', [lockableFt.accountId, 0]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error1.message.includes(`Can't lock 0 or less tokens`));

const error2 = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'lock', [lockableFt.accountId, 10001]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error2.message.includes(`Not enough unlocked balance`));

const error3 = await t.throwsAsync(() => ali.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'lock', [lockableFt.accountId, 10]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error3.message.includes(`Not enough allowance`));
});

test('Unlock owner', async t => {
const { jsvm, lockableFt, ali } = t.context.accounts;
await lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'lock', [lockableFt.accountId, 100]),
{ attachedDeposit: '100000000000000000000000' }
);
await lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'unlock', [lockableFt.accountId, 100]),
{ attachedDeposit: '100000000000000000000000' }
);
const unlockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getUnlockedBalance', [lockableFt.accountId])
);
t.is(unlockedBalance, 10000);
const allowance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getAllowance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(allowance, 0);
const lockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getLockedBalance', [lockableFt.accountId, lockableFt.accountId])
);
t.is(lockedBalance, 0);
});

test('Unlock failures', async t => {
const { jsvm, lockableFt } = t.context.accounts;
const error1 = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'unlock', [lockableFt.accountId, 0]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error1.message.includes(`Can't unlock 0 or less tokens`));

const error2 = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'unlock', [lockableFt.accountId, 1]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error2.message.includes(`Not enough locked tokens`));
});

test('Simple transfer', async t => {
const { jsvm, lockableFt, ali } = t.context.accounts;
await lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'transfer', [ali.accountId, 100]),
{ attachedDeposit: '100000000000000000000000' }
);
const ownerUnlockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getUnlockedBalance', [lockableFt.accountId])
);
t.is(ownerUnlockedBalance, 9900);
const aliUnlockedBalance = await jsvm.view(
'view_js_contract',
encodeCall(lockableFt.accountId, 'getUnlockedBalance', [ali.accountId])
);
t.is(aliUnlockedBalance, 100);
});

test('Transfer failures', async t => {
const { jsvm, lockableFt, ali } = t.context.accounts;
const error1 = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'transfer', [ali.accountId, 0]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error1.message.includes(`Can't transfer 0 or less tokens`));

const error2 = await t.throwsAsync(() => lockableFt.call(
jsvm,
'call_js_contract',
encodeCall(lockableFt.accountId, 'transfer', [ali.accountId, 10001]),
{ attachedDeposit: '100000000000000000000000' }
));
t.assert(error2.message.includes(`Not enough unlocked balance`));
});
8 changes: 8 additions & 0 deletions examples/lockable-fungible-token/ava.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require('util').inspect.defaultOptions.depth = 5; // Increase AVA's printing depth

module.exports = {
timeout: '300000',
files: ['**/*.ava.js'],
failWithoutAssertions: false,
extensions: ['js'],
};
6 changes: 6 additions & 0 deletions examples/lockable-fungible-token/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"plugins": [
"near-sdk-js/src/build-tools/near-bindgen-exporter",
["@babel/plugin-proposal-decorators", {"version": "legacy"}]
]
}
Loading

0 comments on commit 182ee66

Please sign in to comment.