Skip to content

Commit

Permalink
wirte workspaces tests and fix some bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
itegulov committed May 23, 2022
1 parent 819b05f commit 12cb829
Show file tree
Hide file tree
Showing 5 changed files with 2,331 additions and 1,502 deletions.
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 status-message JS contract
const lockableFt = await root.createSubAccount('status-message');
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'],
};
8 changes: 6 additions & 2 deletions examples/lockable-fungible-token/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
"main": "index.js",
"type": "module",
"scripts": {
"build": "near-sdk build"
"build": "near-sdk build",
"test": "ava"
},
"author": "Near Inc <hello@nearprotocol.com>",
"license": "Apache-2.0",
"dependencies": {
"near-sdk-js": "file:../../"
},
"devDependencies": {}
"devDependencies": {
"ava": "^4.2.0",
"near-workspaces": "^2.0.0"
}
}
27 changes: 12 additions & 15 deletions examples/lockable-fungible-token/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,35 @@ class Account {

setAllowance(escrowAccountId, allowance) {
if (allowance > 0) {
this.allowances.set(escrowAccountId, allowance)
this.allowances[escrowAccountId] = allowance
} else if (allowance === 0) {
this.allowances.delete(escrowAccountId)
delete this.allowances[escrowAccountId]
} else {
throw Error("Allowance can't be negative")
}
}

getAllowance(escrowAccountId) {
return this.allowances.get(escrowAccountId) || 0
return this.allowances[escrowAccountId] || 0
}

setLockedBalance(escrowAccountId, lockedBalance) {
if (lockedBalance > 0) {
this.lockedBalances.set(escrowAccountId, lockedBalance)
this.lockedBalances[escrowAccountId] = lockedBalance
} else if (lockedBalance === 0) {
this.lockedBalances.delete(escrowAccountId)
delete this.lockedBalances[escrowAccountId]
} else {
throw Error("Locked balance cannot be negative")
}
}

getLockedBalance(escrowAccountId) {
return this.lockedBalances.get(escrowAccountId) || 0
return this.lockedBalances[escrowAccountId] || 0
}

totalBalance() {
var totalLockedBalance = 0
this.lockedBalances.forEach((v) => {
totalLockedBalance += v
})
let totalLockedBalance =
Object.values(this.lockedBalances).reduce((acc, val) => acc + val, 0)
return this.balance + totalLockedBalance
}
}
Expand All @@ -79,10 +77,9 @@ class LockableFungibleToken extends NearContract {
getAccount(ownerId) {
let account = this.accounts.get(ownerId)
if (account === null) {
return new Account(0, new Map(), new Map())
return new Account(0, {}, {})
}
let { balance, allowances, lockedBalances } = JSON.parse(account)
return new Account(balance, new Map(Object.entries(allowances)), new Map(Object.entries(lockedBalances)))
return Object.assign(new Account, JSON.parse(account))
}

setAccount(accountId, account) {
Expand All @@ -107,8 +104,8 @@ class LockableFungibleToken extends NearContract {

@call
lock(ownerId, lockAmount) {
if (lockAmount === 0) {
throw Error("Can't lock 0 tokens")
if (lockAmount <= 0) {
throw Error("Can't lock 0 or less tokens")
}
let escrowAccountId = near.predecessorAccountId()
let account = this.getAccount(ownerId)
Expand Down
Loading

0 comments on commit 12cb829

Please sign in to comment.