tests: staking integration test

This commit is contained in:
ppedziwiatr
2021-10-26 15:10:50 +02:00
committed by Piotr Pędziwiatr
parent f139cfdda4
commit 1663bcbaeb
9 changed files with 415 additions and 10 deletions

View File

@@ -24,12 +24,12 @@ describe('Testing the SmartWeave client', () => {
beforeAll(async () => {
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
arlocal = new ArLocal(1950, false);
arlocal = new ArLocal(1800, false);
await arlocal.start();
arweave = Arweave.init({
host: 'localhost',
port: 1950,
port: 1800,
protocol: 'http'
});

View File

@@ -0,0 +1,47 @@
/**
*/
export async function handle(state, action) {
const _minimumStake = state.minimumStake;
const _unstakePeriod = state.unstakePeriod;
const _stakes = state.stakes;
const tokenTxId = state.tokenTxId;
const _input = action.input;
const _msgSender = action.caller;
if (_input.function === 'stake') {
const amount = _input.amount;
if (amount < _minimumStake) {
throw new ContractError(`You must stake at least ${_minimumStake} tokens`);
}
// TODO: use "view" functions here
const tokenState = await SmartWeave.contracts.readContractState(tokenTxId);
if (tokenState.balances[_msgSender] < amount) {
throw new ContractError('Cannot stake more token than you hold unstaked');
}
if (!tokenState.allowances[_msgSender]) {
throw new ContractError('Caller must increase their allowance');
}
const allowance = tokenState.allowances[_msgSender][SmartWeave.contract.id];
if (allowance < amount) {
throw new ContractError('Caller must increase their allowance');
}
await SmartWeave.contracts.write(tokenTxId, {
function: 'transferFrom',
sender: _msgSender,
recipient: SmartWeave.contract.id,
amount
});
_stakes[_msgSender] = {
amount: amount,
unlockWhen: 0
};
return { state };
}
throw new ContractError(`No function supplied or function not recognised: "${_input.function}"`);
}

View File

@@ -0,0 +1,6 @@
{
"minimumStake": 1000,
"unstakePeriod": 10,
"stakes": {},
"tokenTxId": ""
}

View File

@@ -0,0 +1,103 @@
/**
* This is an example token contract that mimics the
* allowance/transferFrom features from ERC-20.
*
* note: most validations have been removed for code brevity.
*/
export function handle(state, action) {
const _balances = state.balances;
const _allowances = state.allowances;
const _input = action.input;
const _msgSender = action.caller;
if (_input.function === 'transfer') {
const recipient = _input.recipient;
const amount = _input.amount;
return _transfer(_msgSender, recipient, amount);
}
if (_input.function === 'approve') {
const spender = _input.spender;
const amount = _input.amount;
if (!_allowances[_msgSender]) {
_allowances[_msgSender] = {};
}
if (!_allowances[_msgSender][spender]) {
_allowances[_msgSender][spender] = {};
}
_allowances[_msgSender][spender] = amount;
return { state };
}
if (_input.function === 'mint') {
const account = _input.account;
const amount = _input.amount;
state.totalSupply += amount;
if (account in _balances) {
_balances[account] += amount;
} else {
_balances[account] = amount;
}
return { state };
}
if (_input.function === 'allowance') {
const owner = _input.owner;
const spender = _input.spender;
let allowance = 0;
if (_allowances[owner] && _allowances[owner][spender]) {
allowance = _allowances[owner][spender];
}
return { result: allowance };
}
if (_input.function === 'transferFrom') {
const sender = _input.sender;
const recipient = _input.recipient;
const amount = _input.amount;
const currentAllowance = _allowances[sender][_msgSender];
if (currentAllowance < amount) {
throw new ContractError(`Transfer amount exceeds allowance`);
}
return _transfer(sender, recipient, amount);
}
if (_input.function === 'balance') {
const target = _input.target;
const ticker = state.ticker;
return { result: { target, ticker, balance: _balances[target] } };
}
throw new ContractError(`No function supplied or function not recognised: "${_input.function}"`);
function _transfer(sender, recipient, amount) {
if (amount <= 0 || sender === recipient) {
throw new ContractError('Invalid token transfer');
}
if (_balances[sender] < amount) {
throw new ContractError(`Caller balance not high enough to send ${amount} token(s)!`);
}
_balances[sender] -= amount;
if (recipient in _balances) {
_balances[recipient] += amount;
} else {
_balances[recipient] = amount;
}
return { state };
}
}

View File

@@ -0,0 +1,9 @@
{
"ticker": "EXAMPLE_PST_TOKEN",
"owner": "uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M",
"totalSupply": 0,
"balances": {
},
"allowances": {
}
}

View File

@@ -34,12 +34,12 @@ describe('Testing the SmartWeave client', () => {
beforeAll(async () => {
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
arlocal = new ArLocal(1940, false);
arlocal = new ArLocal(1810, false);
await arlocal.start();
arweave = Arweave.init({
host: 'localhost',
port: 1940,
port: 1810,
protocol: 'http'
});

View File

@@ -0,0 +1,244 @@
import fs from 'fs';
import ArLocal from 'arlocal';
import Arweave from 'arweave';
import { JWKInterface } from 'arweave/node/lib/wallet';
import { Contract, LoggerFactory, SmartWeave, SmartWeaveNodeFactory } from '@smartweave';
import path from 'path';
import { TsLogFactory } from '../../../logging/node/TsLogFactory';
/**
*/
describe('Testing internal writes', () => {
let tokenContractSrc: string;
let tokenContractInitialState: string;
let tokenContract: Contract<any>;
let tokenContractTxId;
let stakingContractSrc: string;
let stakingContractInitialState: string;
let stakingContract: Contract<any>;
let stakingContractTxId;
let wallet: JWKInterface;
let walletAddress: string;
let arweave: Arweave;
let arlocal: ArLocal;
let smartweave: SmartWeave;
beforeAll(async () => {
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
arlocal = new ArLocal(1950, false);
await arlocal.start();
arweave = Arweave.init({
host: 'localhost',
port: 1950,
protocol: 'http'
});
LoggerFactory.use(new TsLogFactory());
LoggerFactory.INST.logLevel('error');
});
afterAll(async () => {
await arlocal.stop();
});
async function deployContracts() {
smartweave = SmartWeaveNodeFactory.memCached(arweave);
wallet = await arweave.wallets.generate();
walletAddress = await arweave.wallets.jwkToAddress(wallet);
tokenContractSrc = fs.readFileSync(path.join(__dirname, '../data/staking/token-allowance.js'), 'utf8');
tokenContractInitialState = fs.readFileSync(path.join(__dirname, '../data/staking/token-allowance.json'), 'utf8');
stakingContractSrc = fs.readFileSync(path.join(__dirname, '../data/staking/staking-contract.js'), 'utf8');
stakingContractInitialState = fs.readFileSync(
path.join(__dirname, '../data/staking/staking-contract.json'),
'utf8'
);
tokenContractTxId = await smartweave.createContract.deploy({
wallet,
initState: JSON.stringify({
...JSON.parse(tokenContractInitialState),
owner: walletAddress
}),
src: tokenContractSrc
});
stakingContractTxId = await smartweave.createContract.deploy({
wallet,
initState: JSON.stringify({
...JSON.parse(stakingContractInitialState),
tokenTxId: tokenContractTxId
}),
src: stakingContractSrc
});
tokenContract = smartweave.contract(tokenContractTxId).connect(wallet);
stakingContract = smartweave.contract(stakingContractTxId).connect(wallet);
await mine();
}
describe('with read states in between', () => {
beforeAll(async () => {
await deployContracts();
});
it('should deploy contracts with initial state', async () => {
expect((await tokenContract.readState()).state).toEqual({
allowances: {},
balances: {},
owner: walletAddress,
ticker: 'EXAMPLE_PST_TOKEN',
totalSupply: 0
});
expect((await stakingContract.readState()).state).toEqual({
minimumStake: 1000,
stakes: {},
tokenTxId: tokenContractTxId,
unstakePeriod: 10
});
});
it('should mint tokens', async () => {
await tokenContract.writeInteraction({
function: 'mint',
account: walletAddress,
amount: 10000
});
await mine();
const tokenState = (await tokenContract.readState()).state;
expect(tokenState.balances).toEqual({
[walletAddress]: 10000
});
expect(tokenState.totalSupply).toEqual(10000);
});
it('should not stake tokens if no allowance', async () => {
await stakingContract.writeInteraction({
function: 'stake',
amount: 1000
});
await mine();
expect((await stakingContract.readState()).state.stakes).toEqual({
});
const tokenState = (await tokenContract.readState()).state;
expect(tokenState.balances).toEqual({
[walletAddress]: 10000,
});
});
it('should approve for staking contract', async () => {
await tokenContract.writeInteraction({
function: 'approve',
spender: stakingContractTxId,
amount: 1000
});
await mine();
expect((await tokenContract.readState()).state.allowances).toEqual({
[walletAddress]: {
[stakingContractTxId]: 1000
}
});
});
it('should stake tokens', async () => {
await stakingContract.writeInteraction({
function: 'stake',
amount: 1000
});
await mine();
expect((await stakingContract.readState()).state.stakes).toEqual({
[walletAddress]: {
amount: 1000,
unlockWhen: 0
}
});
const tokenState = (await tokenContract.readState()).state;
expect(tokenState.balances).toEqual({
[walletAddress]: 9000,
[stakingContractTxId]: 1000
});
});
});
describe('with read states at the end', () => {
beforeAll(async () => {
await deployContracts();
});
it('should stake tokens', async () => {
expect((await tokenContract.readState()).state).toEqual({
allowances: {},
balances: {},
owner: walletAddress,
ticker: 'EXAMPLE_PST_TOKEN',
totalSupply: 0
});
expect((await stakingContract.readState()).state).toEqual({
minimumStake: 1000,
stakes: {},
tokenTxId: tokenContractTxId,
unstakePeriod: 10
});
await tokenContract.writeInteraction({
function: 'mint',
account: walletAddress,
amount: 10000
});
await mine();
await stakingContract.writeInteraction({
function: 'stake',
amount: 1000
});
await mine();
await tokenContract.writeInteraction({
function: 'approve',
spender: stakingContractTxId,
amount: 1000
});
await mine();
await stakingContract.writeInteraction({
function: 'stake',
amount: 1000
});
await mine();
const tokenState = (await tokenContract.readState()).state;
expect(tokenState.balances).toEqual({
[walletAddress]: 9000,
[stakingContractTxId]: 1000
});
expect((await stakingContract.readState()).state.stakes).toEqual({
[walletAddress]: {
amount: 1000,
unlockWhen: 0
}
});
});
});
async function mine() {
await arweave.api.get('mine');
}
});

View File

@@ -22,12 +22,12 @@ describe('Testing the Profit Sharing Token', () => {
beforeAll(async () => {
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
arlocal = new ArLocal(1930, false);
arlocal = new ArLocal(1820, false);
await arlocal.start();
arweave = Arweave.init({
host: 'localhost',
port: 1930,
port: 1820,
protocol: 'http'
});

View File

@@ -46,10 +46,6 @@ export class SmartWeaveGlobal {
_activeTx?: GQLNodeInterface;
get _isDryRunning() {
return !this._activeTx;
}
constructor(arweave: Arweave, contract: { id: string; owner: string }) {
this.unsafeClient = arweave;
this.arweave = {