tests: staking integration test
This commit is contained in:
committed by
Piotr Pędziwiatr
parent
f139cfdda4
commit
1663bcbaeb
@@ -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'
|
||||
});
|
||||
|
||||
|
||||
47
src/__tests__/integration/data/staking/staking-contract.js
Normal file
47
src/__tests__/integration/data/staking/staking-contract.js
Normal 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}"`);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"minimumStake": 1000,
|
||||
"unstakePeriod": 10,
|
||||
"stakes": {},
|
||||
"tokenTxId": ""
|
||||
}
|
||||
103
src/__tests__/integration/data/staking/token-allowance.js
Normal file
103
src/__tests__/integration/data/staking/token-allowance.js
Normal 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 };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ticker": "EXAMPLE_PST_TOKEN",
|
||||
"owner": "uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M",
|
||||
"totalSupply": 0,
|
||||
"balances": {
|
||||
},
|
||||
"allowances": {
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
|
||||
244
src/__tests__/integration/internal-writes/staking.test.ts
Normal file
244
src/__tests__/integration/internal-writes/staking.test.ts
Normal 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');
|
||||
}
|
||||
});
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user