const MasterChef = artifacts.require('MasterChef'); const StakingRewards = artifacts.require('StakingRewards'); const TokenMock = artifacts.require('TokenMock'); const { expectEvent, expectRevert, time, ether, BN, } = require('@openzeppelin/test-helpers'); const {use, expect} = require('chai'); function bn(params) { return new BN(params.toString()); } use(require('chai-bn')(BN)); const PID = '0'; const ALLOCATE_POINT_SUSHI = ether('1000'); contract( 'Staking', ([owner, dev, wallet1, wallet2, wallet3]) => { let slp; let sushi; let xft; let onsen; let staking; beforeEach(async () => { slp = await TokenMock.new('SLP', 'SLP', ether('10000000'), {from: owner}); sushi = await TokenMock.new('SUSHI', 'SUSHI', ether('10000000'), {from: owner}); xft = await TokenMock.new('XFT', 'XFT', ether('10000000'), {from: owner}); onsen = await MasterChef.new( sushi.address, dev, ether('0.01'), await time.latestBlock(), (await time.latestBlock()) + 2592000, // apx. 12 month {from: owner}, ); staking = await StakingRewards.new( xft.address, sushi.address, slp.address, onsen.address, PID, {from: owner}, ); await onsen.add(ALLOCATE_POINT_SUSHI, slp.address, false, {from: owner}); await sushi.mint(onsen.address, ALLOCATE_POINT_SUSHI); await slp.mint(wallet1, ether('10000000000'), {from: owner}); await slp.mint(wallet2, ether('10000000000'), {from: owner}); await slp.mint(wallet3, ether('10000000000'), {from: owner}); await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet1}); await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet2}); await slp.approve(staking.address, new BN(2).pow(new BN(255)), {from: wallet3}); }); describe('check basic init', () => { it('should init PID', async () => { expect(await staking.PID()).to.be.a.bignumber.equal(PID); }); it('should init periodFinish', async () => { expect(await staking.periodFinish()).to.be.a.bignumber.equal('0'); }); it('should init rewardRate', async () => { expect(await staking.rewardRate()).to.be.a.bignumber.equal('0'); }); it('should init rewardsDuration', async () => { const duration = await time.duration.days(30); expect(await staking.rewardsDuration()).to.be.a.bignumber.equal(duration); }); describe('constructor', () => { it('should set SLP token address', async () => { expect(await staking.stakingToken()).to.be.eq(slp.address); }); it('should set XFT token address', async () => { expect(await staking.rewardsTokenXFT()).to.be.eq(xft.address); }); it('should set SUSHI token address', async () => { expect(await staking.rewardsTokenSUSHI()).to.be.eq(sushi.address); }); it('should set Onsen contract address', async () => { expect(await staking.masterChef()).to.be.eq(onsen.address); }); }); }); describe('Functions', () => { describe('lastTimeRewardApplicable', () => { it('should return zero', async () => { expect( await staking.lastTimeRewardApplicable(), ).to.be.a.bignumber.equal('0'); }); it('should return ~current timestamp', async () => { await xft.transfer(staking.address, ether('100'), {from: owner}); await staking.notifyRewardAmount(ether('100'), {from: owner}); expect( await staking.lastTimeRewardApplicable(), ).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100'); }); it('should return periodFinish', async () => { expect(await staking.periodFinish()).to.be.a.bignumber.equal('0'); expect(await staking.lastTimeRewardApplicable()).to.be.a.bignumber.eq( '0', ); await xft.transfer(staking.address, ether('100'), {from: owner}); await staking.notifyRewardAmount(ether('100'), {from: owner}); const periodFinish = await new BN(await time.latest()).add( await time.duration.days(30), ); expect( await staking.lastTimeRewardApplicable(), ).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100'); await xft.transfer(staking.address, ether('100'), {from: owner}); await staking.notifyRewardAmount(ether('100'), {from: owner}); await time.increase(await time.duration.days(40)); expect( await staking.periodFinish(), ).to.be.a.bignumber.that.is.at.closeTo(periodFinish, '100'); expect( await staking.lastTimeRewardApplicable(), ).to.be.a.bignumber.that.is.at.closeTo( await staking.periodFinish(), '100', ); }); }); describe('rewardPerToken', () => { beforeEach(async () => { await xft.mint(staking.address, ether('100000'), {from: owner}); }); it('should return zero if no one stake', async () => { expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0'); }); it('should return reward per token', async () => { await staking.notifyRewardAmount(ether('1'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0'); await time.increase(await time.duration.days(30)); expect( await staking.rewardPerToken(), ).to.be.a.bignumber.that.is.at.closeTo(ether('0.5'), ether('0.01')); }); it('should calculate reword per token correctly', async () => { await staking.notifyRewardAmount(ether('40'), {from: owner}); expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0'); await staking.stake(ether('2'), {from: wallet1}); await staking.stake(ether('4'), {from: wallet2}); await staking.stake(ether('6'), {from: wallet3}); await staking.stake(ether('8'), {from: wallet1}); await time.increase(await time.duration.days(30)); expect( await staking.rewardPerToken(), ).to.be.a.bignumber.that.is.at.closeTo(ether('2'), ether('0.001')); }); it('should calculate reword per token correctly with small amount of tokens', async () => { await staking.notifyRewardAmount(ether('2'), {from: owner}); expect(await staking.rewardPerToken()).to.be.a.bignumber.equal('0'); await staking.stake(6, {from: wallet1}); await staking.stake(4, {from: wallet2}); await time.increase(await time.duration.days(30)); expect( await staking.rewardPerToken(), ).to.be.a.bignumber.that.is.at.closeTo( ether('200000000000000000'), ether('1000000000000'), ); }); }); describe('earnedXFT', () => { beforeEach(async () => { await xft.mint(staking.address, ether('100000'), {from: owner}); }); describe('should calculate reward correctly', () => { it('when no stakes', async () => { expect(await staking.earnedXFT(wallet1)).to.be.a.bignumber.equal( '0', ); }); it('when no reward amount', async () => { await staking.stake(ether('10'), {from: wallet1}); expect(await staking.earnedXFT(wallet1)).to.be.a.bignumber.equal( '0', ); await time.increase(await time.duration.days(30)); expect(await staking.earnedXFT(wallet1)).to.be.a.bignumber.equal( '0', ); }); it('when stake small amount', async () => { await xft.mint(staking.address, ether('100000'), {from: owner}); await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(4, {from: wallet1}); await staking.stake(6, {from: wallet2}); await time.increase(await time.duration.days(30)); expect( await staking.earnedXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo(ether('4'), ether('0.001')); expect( await staking.earnedXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo(ether('6'), ether('0.001')); }); it('when stake big amount', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2000000000'), {from: wallet1}); await staking.stake(ether('8000000000'), {from: wallet2}); await time.increase(await time.duration.days(30)); expect( await staking.earnedXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo(ether('2'), ether('0.001')); expect( await staking.earnedXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo(ether('8'), ether('0.001')); }); it('when stake different amount a few times', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); await staking.stake(ether('2'), {from: wallet2}); await time.increase(await time.duration.days(30)); expect( await staking.earnedXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001')); expect( await staking.earnedXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001')); expect(await staking.earnedXFT(wallet3)).to.be.a.bignumber.equal( '0', ); await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); await staking.stake(ether('4'), {from: wallet3}); await time.increase(await time.duration.days(30)); expect( await staking.earnedXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('9'), // 5 + 4 ether('0.001'), ); expect( await staking.earnedXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('7'), // 5 + 2 ether('0.001'), ); expect( await staking.earnedXFT(wallet3), ).to.be.a.bignumber.that.is.at.closeTo( ether('4'), // 0 + 4 ether('0.001'), ); }); it('when stake and withdraw', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); await staking.stake(ether('2'), {from: wallet2}); await time.increase(await time.duration.days(30)); expect( await staking.earnedXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001')); expect( await staking.earnedXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo(ether('5'), ether('0.001')); await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.withdraw(ether('2'), {from: wallet1}); await staking.stake(ether('6'), {from: wallet1}); await staking.stake(ether('2'), {from: wallet2}); await time.increase(await time.duration.days(30)); expect( await staking.earnedXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('11'), // 5 + 6 ether('0.001'), ); expect( await staking.earnedXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('9'), // 5 + 4 ether('0.001'), ); }); }); }); describe('earnedSushi', () => { beforeEach(async () => { await xft.mint(staking.address, ether('100000'), {from: owner}); }); describe('should calculate reward correctly', () => { it('when no stakes', async () => { expect(await staking.earnedSushi(wallet1)).to.be.a.bignumber.equal( '0', ); }); it('when stake small amount', async () => { await xft.mint(staking.address, ether('100000'), {from: owner}); await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(4, {from: wallet1}); await staking.stake(6, {from: wallet2}); await time.advanceBlock(); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.14'), ether('0.001'), ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.06'), ether('0.001'), ); }); it('when stake big amount', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2000000000'), {from: wallet1}); await staking.stake(ether('8000000000'), {from: wallet2}); await time.advanceBlock(); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.12'), ether('0.001'), ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.08'), ether('0.001'), ); }); it('should calculate reward correctly', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); await time.advanceBlock(); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.1'), ether('0.001'), ); expect( await onsen.pendingSushi(PID, staking.address), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.1'), ether('0.001'), ); await staking.stake(ether('2'), {from: wallet1}); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( ether('0.2'), ); expect( await onsen.pendingSushi(PID, staking.address), ).to.be.a.bignumber.equal('0'); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.2'), ether('0.001'), ); await time.advanceBlock(); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( ether('0.2'), ); expect( await onsen.pendingSushi(PID, staking.address), ).to.be.a.bignumber.equal(ether('0.1')); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.3'), ether('0.001'), ); }); it('when stake different amount a few times', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); // 1 block await staking.stake(ether('2'), {from: wallet2}); // 2 block expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.1'), // only 1 block mined ether('0.001'), ); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( '0', ); await time.advanceBlock(); // 3 block expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.15'), // 2 blocks mined ether('0.001'), ); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( '0', ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.05'), // for 1 block ether('0.001'), ); expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal( '0', ); await staking.stake(ether('2'), {from: wallet1}); // 4 block expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( ether('0.2'), ); expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal( '0', ); await staking.stake(ether('4'), {from: wallet3}); // 5 block expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal( '0', ); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.26666'), // 4 blocks mined ether('0.00001'), ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.13333'), ether('0.00001'), ); expect(await staking.earnedSushi(wallet3)).to.be.a.bignumber.equal( '0', ); await time.advanceBlock(); // 6 block expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.30666'), // 5 blocks mined ether('0.00001'), ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.15333'), ether('0.00001'), ); expect( await staking.earnedSushi(wallet3), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.04'), ether('0.00001'), ); }); it('when stake and withdraw', async () => { await staking.notifyRewardAmount(ether('10'), {from: owner}); await staking.stake(ether('2'), {from: wallet1}); await staking.stake(ether('2'), {from: wallet2}); await time.advanceBlock(); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.15'), ether('0.001'), ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.05'), ether('0.001'), ); await staking.withdraw(ether('2'), {from: wallet1}); await time.advanceBlock(); await time.advanceBlock(); expect( await staking.earnedSushi(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.2'), ether('0.001'), ); expect( await staking.earnedSushi(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.3'), ether('0.001'), ); }); }); }); describe('stake', () => { it('should revert if amount == 0', async () => { await expectRevert( staking.stake(0, {from: wallet1}), 'Stake: cant stake 0', ); }); it('should revert if stake on pause', async () => { expect(await staking.paused()).to.be.equal(false); await staking.pause({from: owner}); expect(await staking.paused()).to.be.equal(true); await expectRevert( staking.stake(ether('10'), {from: wallet1}), 'Pausable: paused', ); }); it('should increase _totalStaked', async () => { expect(await staking.totalStaked()).to.be.a.bignumber.equal('0'); await staking.stake(ether('2'), {from: wallet1}); expect(await staking.totalStaked()).to.be.a.bignumber.equal( ether('2'), ); await staking.stake(ether('5'), {from: wallet2}); expect(await staking.totalStaked()).to.be.a.bignumber.equal( ether('7'), ); }); describe('should transfer tokens', () => { it('check balances', async () => { expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( '0', ); expect( await slp.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.stake(ether('1000000000'), {from: wallet1}), expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('9000000000'), ); expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( ether('1000000000'), ); expect( await slp.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); // transfer user -> staking -> onsen const logs = await slp .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[0].args['from']).to.be.equal(wallet1); expect(await logs[0].args['to']).to.be.equal(staking.address); expect(await logs[0].args['value']).to.be.a.bignumber.equal( ether('100'), ); expect(await logs[1].args['from']).to.be.equal(staking.address); expect(await logs[1].args['to']).to.be.equal(onsen.address); expect(await logs[1].args['value']).to.be.a.bignumber.equal( ether('100'), ); }); }); it('should catch event Approval', async () => { await staking.stake(ether('100'), {from: wallet1}); // transfer user -> staking -> onsen const logs = await slp .getPastEvents('Approval', {toBlock: 'latest'}) .then((events) => { return events[1].args; }); expect(await logs['owner']).to.be.equal(staking.address); expect(await logs['spender']).to.be.equal(onsen.address); expect(await logs['value']).to.be.a.bignumber.equal(ether('100')); }); describe('should stake to onsen contract', () => { it('check balances', async () => { expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( '0', ); await staking.stake(ether('1000000000'), {from: wallet1}), expect( await slp.balanceOf(onsen.address), ).to.be.a.bignumber.equal(ether('1000000000')); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); // transfer user -> staking -> onsen const logs = await slp .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events[1].args; }); expect(await logs['from']).to.be.equal(staking.address); expect(await logs['to']).to.be.equal(onsen.address); expect(await logs['value']).to.be.a.bignumber.equal(ether('100')); }); }); // TODO: if multiple stake describe('should transfer reward in SUSHI to staking contract', () => { it('check balances', async () => { await staking.stake(ether('1000000000'), {from: wallet1}); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); expect( await sushi.balanceOf(onsen.address), ).to.be.a.bignumber.equal(ether('1000')); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.withdraw(ether('1000000000'), {from: wallet1}); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); expect( await sushi.balanceOf(onsen.address), ).to.be.a.bignumber.equal(ether('1000')); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0.1')); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); await staking.withdraw(ether('100'), {from: wallet1}); // transfer onsen -> staking and hold const logs = await sushi .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[2].args['from']).to.be.equal(onsen.address); expect(await logs[2].args['to']).to.be.equal(staking.address); expect(await logs[2].args['value']).to.be.a.bignumber.equal( ether('0.1'), ); }); }); // TODO: it('should add stake amount to userInfo', async () => { expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal('0'); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal('0'); await staking.stake(ether('2'), {from: wallet1}); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('2')); await staking.stake(ether('5'), {from: wallet2}); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal(ether('5')); await staking.stake(ether('3'), {from: wallet1}); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('5')); }); it('should update rewardDebt', async () => { let userInfo = await staking.userInfo(wallet1); expect(userInfo.rewardDebt).to.be.a.bignumber.equal('0'); await staking.stake(ether('100'), {from: wallet1}); await staking.stake(ether('100'), {from: wallet1}); userInfo = await staking.userInfo(wallet1); const userInfoOnsen = await staking.userInfo(wallet1); // let rewardDebt = userInfo.amount.mul(poolInfo.accSushiPerShare).div(bn(1e12)); expect(userInfo.rewardDebt).to.be.a.bignumber.equal( userInfoOnsen.rewardDebt, ); }); it('should cath event Staked', async () => { const receipt = await staking.stake(ether('2'), {from: wallet1}); expectEvent.inLogs(receipt.logs, 'Staked', { user: wallet1, amount: ether('2'), }); }); }); describe('withdraw', () => { it('should revert if amount == 0', async () => { await expectRevert( staking.withdraw(0, {from: wallet1}), 'Withdraw: cant withdraw 0', ); }); it('should fail if withdraw > staked', async () => { await expectRevert( staking.withdraw(ether('20'), {from: wallet1}), 'Withdraw: insufficient funds', ); await staking.stake(ether('20'), {from: wallet1}); await expectRevert( staking.withdraw(ether('25'), {from: wallet1}), 'Withdraw: insufficient funds', ); }); it('should decrease _totalStaked', async () => { expect(await staking.totalStaked()).to.be.a.bignumber.equal('0'); await staking.stake(ether('20'), {from: wallet1}); expect(await staking.totalStaked()).to.be.a.bignumber.equal( ether('20'), ); await staking.withdraw(ether('5'), {from: wallet1}); expect(await staking.totalStaked()).to.be.a.bignumber.equal( ether('15'), ); }); describe('should withdraw from onsen contract', () => { it('check balances', async () => { await staking.stake(ether('1000000000'), {from: wallet1}); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('9000000000'), ); expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( ether('1000000000'), ); expect( await slp.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.withdraw(ether('1000000000'), {from: wallet1}); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( '0', ); expect( await slp.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); await staking.withdraw(ether('100'), {from: wallet1}); // transfer user -> staking -> onsen const logs = await slp .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[0].args['from']).to.be.equal(onsen.address); expect(await logs[0].args['to']).to.be.equal(staking.address); expect(await logs[0].args['value']).to.be.a.bignumber.equal( ether('100'), ); expect(await logs[1].args['from']).to.be.equal(staking.address); expect(await logs[1].args['to']).to.be.equal(wallet1); expect(await logs[1].args['value']).to.be.a.bignumber.equal( ether('100'), ); }); }); it('should update rewardsSushi', async () => { expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( ether('0'), ); await staking.stake(ether('100'), {from: wallet1}); await time.advanceBlock(); await time.advanceBlock(); const sushiPerBlock = await onsen.sushiPerBlock.call(); const pendingSushi = await onsen.pendingSushi.call( PID, staking.address, ); await staking.withdraw(ether('100'), {from: wallet1}); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( pendingSushi.add(sushiPerBlock.mul(bn('10'))), ); // bonus 10x }); describe('should transfer reward in SUSHI to staking contract', () => { it('check balances', async () => { await staking.stake(ether('1000000000'), {from: wallet1}); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); expect( await sushi.balanceOf(onsen.address), ).to.be.a.bignumber.equal(ether('1000')); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.withdraw(ether('1000000000'), {from: wallet1}); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); expect( await sushi.balanceOf(onsen.address), ).to.be.a.bignumber.equal(ether('1000')); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0.1')); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); await staking.withdraw(ether('100'), {from: wallet1}); // transfer onsen -> staking and hold const logs = await sushi .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[2].args['from']).to.be.equal(onsen.address); expect(await logs[2].args['to']).to.be.equal(staking.address); expect(await logs[2].args['value']).to.be.a.bignumber.equal( ether('0.1'), ); }); }); it('should subtract stake amount from userInfo', async () => { await staking.stake(ether('5'), {from: wallet1}); await staking.stake(ether('5'), {from: wallet2}); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('5')); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal(ether('5')); await staking.withdraw(ether('2'), {from: wallet1}); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('3')); await staking.withdraw(ether('5'), {from: wallet2}); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal('0'); await staking.withdraw(ether('3'), {from: wallet1}); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal('0'); }); it('should update rewardDebt', async () => { let userInfoOnsen; let userInfo = await staking.userInfo(wallet1); await staking.stake(ether('100'), {from: wallet1}); await staking.stake(ether('100'), {from: wallet1}); userInfo = await staking.userInfo(wallet1); userInfoOnsen = await staking.userInfo(wallet1); expect(userInfo.rewardDebt).to.be.a.bignumber.equal( userInfoOnsen.rewardDebt, ); await staking.withdraw(ether('100'), {from: wallet1}); await staking.withdraw(ether('100'), {from: wallet1}); userInfo = await staking.userInfo(wallet1); userInfoOnsen = await staking.userInfo(wallet1); expect(userInfo.rewardDebt).to.be.a.bignumber.equal( userInfoOnsen.rewardDebt, ); }); describe('should transfer withdraw amount to member', () => { it('check balances', async () => { await staking.stake(ether('1000000000'), {from: wallet1}); expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( ether('1000000000'), ); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('9000000000'), ); expect( await slp.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.withdraw(ether('1000000000'), {from: wallet1}); expect(await slp.balanceOf(onsen.address)).to.be.a.bignumber.equal( '0', ); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect( await slp.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); await time.advanceBlock(); await staking.withdraw(ether('100'), {from: wallet1}); // transfer onsen -> staking -> user const logs = await slp .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events[1].args; }); expect(await logs['from']).to.be.equal(staking.address); expect(await logs['to']).to.be.equal(wallet1); expect(await logs['value']).to.be.a.bignumber.equal(ether('100')); }); }); describe('should transfer SUSHI reward to staking contract', () => { it('check balances', async () => { await staking.stake(ether('1000000000'), {from: wallet1}); expect( await sushi.balanceOf(onsen.address), ).to.be.a.bignumber.equal(ether('1000')); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.withdraw(ether('1000000000'), {from: wallet1}); // expect(await sushi.balanceOf(onsen.address)).to.be.a.bignumber.equal(ether("999.9")); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0.1')); }); it('should catch event Transfer', async () => { await staking.stake(ether('100'), {from: wallet1}); await time.advanceBlock(); await staking.withdraw(ether('100'), {from: wallet1}); // transfer user -> staking -> onsen const logs = await sushi .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events[2].args; }); expect(await logs['from']).to.be.equal(onsen.address); expect(await logs['to']).to.be.equal(staking.address); expect(await logs['value']).to.be.a.bignumber.equal(ether('0.2')); }); }); it('should cath event Withdrawn', async () => { await staking.stake(ether('2'), {from: wallet1}); const receipt = await staking.withdraw(ether('2'), {from: wallet1}); expectEvent.inLogs(receipt.logs, 'Withdrawn', { user: wallet1, amount: ether('2'), }); }); }); describe('exit', () => { it('should withdraw all tokens', async () => { expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal('0'); await staking.stake(ether('20'), {from: wallet1}); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('9999999980'), ); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('20')); await staking.exit({from: wallet1}); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('0')); }); it('should get rewards in both tokens', async () => { expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal('0'); expect(await slp.balanceOf(wallet2)).to.be.a.bignumber.equal( ether('10000000000'), ); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal('0'); await xft.mint(staking.address, ether('100000'), {from: owner}); await staking.notifyRewardAmount(ether('1000'), {from: owner}); await staking.stake(ether('20'), {from: wallet1}); await time.advanceBlock(); await staking.stake(ether('80'), {from: wallet2}); await time.advanceBlock(); await time.increase(await time.duration.days(30)); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('9999999980'), ); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('20')); expect(await slp.balanceOf(wallet2)).to.be.a.bignumber.equal( ether('9999999920'), ); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal(ether('80')); await staking.exit({from: wallet1}); await staking.exit({from: wallet2}); expect(await slp.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('10000000000'), ); expect(await slp.balanceOf(wallet2)).to.be.a.bignumber.equal( ether('10000000000'), ); expect(await sushi.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('0.26'), ); expect(await sushi.balanceOf(wallet2)).to.be.a.bignumber.equal( ether('0.34'), ); expect( await xft.balanceOf(wallet1), ).to.be.a.bignumber.that.is.at.closeTo(ether('200'), ether('0.0001')); expect( await xft.balanceOf(wallet2), ).to.be.a.bignumber.that.is.at.closeTo(ether('800'), ether('0.0001')); expect( (await staking.userInfo(wallet1)).amount, ).to.be.a.bignumber.equal(ether('0')); expect( (await staking.userInfo(wallet2)).amount, ).to.be.a.bignumber.equal(ether('0')); }); }); describe('getReward', () => { beforeEach(async () => { await xft.mint(staking.address, ether('100000'), {from: owner}); }); it('should pass if reward == 0', async () => { const receipt = await staking.getReward({from: wallet1}); expect(await receipt.logs).to.have.length(0); }); it('should zeroed rewardsXFT', async () => { await staking.notifyRewardAmount(ether('60'), {from: owner}); await staking.stake(ether('20'), {from: wallet1}); await staking.stake(ether('10'), {from: wallet2}); expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal( '0', ); expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal( '0', ); await time.increase(await time.duration.days(30)); await staking.stake(ether('20'), {from: wallet1}); // to call updateReward await staking.stake(ether('10'), {from: wallet2}); expect( await staking.rewardsXFT(wallet1), ).to.be.a.bignumber.that.is.at.closeTo(ether('40'), ether('0.001')); expect( await staking.rewardsXFT(wallet2), ).to.be.a.bignumber.that.is.at.closeTo(ether('20'), ether('0.001')); await staking.getReward({from: wallet1}); await staking.getReward({from: wallet2}); expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal( '0', ); expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal( '0', ); }); it('should zeroed rewardsSUSHI', async () => { await staking.notifyRewardAmount(ether('60'), {from: owner}); await staking.stake(ether('60'), {from: wallet1}); await staking.stake(ether('40'), {from: wallet2}); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( '0', ); expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal( '0', ); await time.advanceBlock(); await staking.stake(ether('40'), {from: wallet1}); // to call updateReward await staking.stake(ether('60'), {from: wallet2}); await time.advanceBlock(); expect( await staking.rewardsSUSHI(wallet1), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.22'), // 0.1 + 0.06 + 0.06 ether('0.00001'), ); expect( await staking.rewardsSUSHI(wallet2), ).to.be.a.bignumber.that.is.at.closeTo( ether('0.10857'), // 0.04 + 0.04 + 0.02857 ether('0.00001'), ); // thats pending reward before 2 stake await staking.getReward({from: wallet1}); await staking.getReward({from: wallet2}); expect(await staking.rewardsSUSHI(wallet1)).to.be.a.bignumber.equal( '0', ); expect(await staking.rewardsSUSHI(wallet2)).to.be.a.bignumber.equal( '0', ); }); it('should transfer XFT reward', async () => { let logs; await staking.notifyRewardAmount(ether('60'), {from: owner}); await staking.stake(ether('20'), {from: wallet1}); await staking.stake(ether('10'), {from: wallet2}); expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal( '0', ); expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal( '0', ); await time.increase(await time.duration.days(30)); await staking.getReward({from: wallet1}); logs = await xft .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[0].args['from']).to.be.equal(staking.address); expect(await logs[0].args['to']).to.be.equal(wallet1); expect( await logs[0].args['value'], ).to.be.a.bignumber.that.is.at.closeTo(ether('40'), ether('0.001')); await staking.getReward({from: wallet2}); logs = await xft .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[0].args['from']).to.be.equal(staking.address); expect(await logs[0].args['to']).to.be.equal(wallet2); expect( await logs[0].args['value'], ).to.be.a.bignumber.that.is.at.closeTo(ether('20'), ether('0.001')); expect(await staking.rewardsXFT(wallet1)).to.be.a.bignumber.equal( '0', ); expect(await staking.rewardsXFT(wallet2)).to.be.a.bignumber.equal( '0', ); }); it('should transfer SUSHI reward', async () => { let logs; await staking.notifyRewardAmount(ether('60'), {from: owner}); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal('0'); await staking.stake(ether('25'), {from: wallet1}); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0')); await staking.stake(ether('75'), {from: wallet2}); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0.1')); await staking.getReward({from: wallet1}); // make third stake 0, send reward from 2 and 1 stake // 2 blocks mined // reward wallet1: | 0.1 | 0.025 | = 0.125 // reward wallet2: | - | 0.075 | = 0.075 // total 0.3 logs = await sushi .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[2].args['from']).to.be.equal(onsen.address); expect(await logs[2].args['to']).to.be.equal(staking.address); expect( await logs[2].args['value'], ).to.be.a.bignumber.that.is.at.closeTo( ether('0.1'), // the last reward transferred to staking contract ether('0.001'), ); expect(await logs[3].args['from']).to.be.equal(staking.address); expect(await logs[3].args['to']).to.be.equal(wallet1); expect( await logs[3].args['value'], ).to.be.a.bignumber.that.is.at.closeTo( ether('0.125'), // reward for wallet1 ether('0.00001'), ); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0.075')); await staking.getReward({from: wallet2}); // +1 block for wallet2 // reward wallet2: 0.075 + 0.1 // 3 blocks mined // reward wallet1: | 0.1 | 0.025 | 0.025 | = 0.125 payed, +0.025 // reward wallet2: | - | 0.075 | 0.075 | = 0.15 logs = await sushi .getPastEvents('Transfer', {toBlock: 'latest'}) .then((events) => { return events; }); expect(await logs[2].args['from']).to.be.equal(onsen.address); expect(await logs[2].args['to']).to.be.equal(staking.address); expect( await logs[2].args['value'], ).to.be.a.bignumber.that.is.at.closeTo(ether('0.1'), ether('0.001')); expect(await logs[3].args['from']).to.be.equal(staking.address); expect(await logs[3].args['to']).to.be.equal(wallet2); expect( await logs[3].args['value'], ).to.be.a.bignumber.that.is.at.closeTo(ether('0.15'), ether('0.001')); expect( await sushi.balanceOf(staking.address), ).to.be.a.bignumber.equal(ether('0.025')); expect(await staking.earnedSushi(wallet1)).to.be.a.bignumber.equal( ether('0.025'), ); expect(await staking.earnedSushi(wallet2)).to.be.a.bignumber.equal( '0', ); }); it('should cath events RewardPaid', async () => { await staking.notifyRewardAmount(ether('60'), {from: owner}); await staking.stake(ether('20'), {from: wallet1}); await time.increase(await time.duration.days(30)); const receipt = await staking.getReward({from: wallet1}); await expectEvent.inLogs(receipt.logs, 'XFTRewardPaid', { user: wallet1, reward: receipt.logs[0].args.reward, }); expect( await receipt.logs[0].args.reward, ).to.be.a.bignumber.that.is.at.closeTo(ether('60'), ether('0.001')); await expectEvent.inLogs(receipt.logs, 'SUSHIRewardPaid', { user: wallet1, reward: receipt.logs[1].args.reward, }); expect( await receipt.logs[1].args.reward, ).to.be.a.bignumber.that.is.at.closeTo(ether('0.2'), ether('0.001')); }); }); describe('notifyRewardAmount', () => { beforeEach(async () => { await xft.mint(staking.address, ether('10000000'), {from: owner}); }); it('should revert if param reward > balance', async () => { await expectRevert( staking.notifyRewardAmount(ether('60480000000'), {from: owner}), 'Provided reward too high', ); }); it('only owner can set reward', async () => { await expectRevert( staking.notifyRewardAmount(ether('60'), {from: wallet2}), 'Ownable: caller is not the owner', ); }); it('should update rewardRate', async () => { expect(await staking.rewardRate()).to.be.a.bignumber.eq('0'); await staking.notifyRewardAmount(ether('2592000'), {from: owner}); expect(await staking.rewardRate()).to.be.a.bignumber.eq(ether('1')); await time.increase(await time.duration.days(3)); await staking.notifyRewardAmount(ether('2592000'), {from: owner}); expect( await staking.rewardRate(), ).to.be.a.bignumber.that.is.at.closeTo( ether('1.9'), ether('0.00001'), ); await time.increase(await time.duration.days(30)); await staking.notifyRewardAmount(ether('2592000'), {from: owner}); expect(await staking.rewardRate()).to.be.a.bignumber.eq(ether('1')); }); it('should update lastUpdateTime', async () => { expect(await staking.lastUpdateTime()).to.be.a.bignumber.eq('0'); await staking.notifyRewardAmount(ether('604800'), {from: owner}); expect( await staking.lastUpdateTime(), ).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100'); await time.increase(await time.duration.days(3)); await staking.notifyRewardAmount(ether('604800'), {from: owner}); expect( await staking.lastUpdateTime(), ).to.be.a.bignumber.that.is.at.closeTo(await time.latest(), '100'); }); it('should update periodFinish', async () => { const month = await time.duration.days(30); expect(await staking.periodFinish()).to.be.a.bignumber.eq('0'); await staking.notifyRewardAmount(ether('604800'), {from: owner}); expect( await staking.periodFinish(), ).to.be.a.bignumber.that.is.at.closeTo( new BN(await time.latest()).add(month), '100', ); await time.increase(await time.duration.days(3)); await staking.notifyRewardAmount(ether('604800'), {from: owner}); expect( await staking.periodFinish(), ).to.be.a.bignumber.that.is.at.closeTo( new BN(await time.latest()).add(month), '100', ); }); it('should catch event RewardAdded', async () => { const receipt = await staking.notifyRewardAmount(ether('604800'), { from: owner, }); expectEvent.inLogs(receipt.logs, 'RewardAdded', { reward: ether('604800'), }); }); }); describe('pause', () => { it('only owner can pause staking', async () => { await expectRevert( staking.pause({from: wallet1}), 'Ownable: caller is not the owner', ); await staking.pause({from: owner}); }); it('should revert if stake on pause', async () => { expect(await staking.paused()).to.be.equal(false); await staking.pause({from: owner}); expect(await staking.paused()).to.be.equal(true); await expectRevert( staking.stake(ether('10'), {from: wallet1}), 'Pausable: paused', ); }); it('should revert if paused', async () => { expect(await staking.paused()).to.be.equal(false); await staking.pause({from: owner}); expect(await staking.paused()).to.be.equal(true); await expectRevert( staking.pause({from: owner}), 'Pausable: paused', ); }); it('should pause staking', async () => { expect(await staking.paused()).to.be.equal(false); await staking.stake(ether('10'), {from: wallet1}); await staking.pause({from: owner}); expect(await staking.paused()).to.be.equal(true); await expectRevert( staking.stake(ether('10'), {from: wallet1}), 'Pausable: paused', ); }); it('should cath event Paused', async () => { const receipt = await staking.pause({from: owner}); expectEvent.inLogs(receipt.logs, 'Paused', { account: owner, }); }); }); describe('unpause', () => { it('only owner can unpause staking', async () => { await staking.pause({from: owner}); await expectRevert( staking.unpause({from: wallet1}), 'Ownable: caller is not the owner', ); await staking.unpause({from: owner}); }); it('should revert if unpaused', async () => { expect(await staking.paused()).to.be.equal(false); await expectRevert( staking.unpause({from: owner}), 'Pausable: not paused', ); }); it('should unpause staking', async () => { expect(await staking.paused()).to.be.equal(false); await staking.pause({from: owner}); expect(await staking.paused()).to.be.equal(true); await expectRevert( staking.stake(ether('10'), {from: wallet1}), 'Pausable: paused', ); await staking.unpause({from: owner}); await staking.stake(ether('10'), {from: wallet1}); }); it('should cath event Paused', async () => { await staking.pause({from: owner}); const receipt = await staking.unpause({from: owner}); expectEvent.inLogs(receipt.logs, 'Unpaused', { account: owner, }); }); }); describe('updatePeriodFinish', () => { it('can be invoked only by the owner', async () => { const now = bn(Math.floor(Date.now() / 1000)); await expectRevert( staking.updatePeriodFinish(now, {from: wallet1}), 'Ownable: caller is not the owner', ); await staking.updatePeriodFinish(now); }); it('should set periodFinish', async () => { expect(await staking.periodFinish()).to.be.a.bignumber.equal('0'); await xft.mint(staking.address, ether('10000'), {from: owner}); const tx = await staking.notifyRewardAmount(ether('10000'), {from: owner}); const now = (await web3.eth.getBlock(tx.receipt.blockNumber)).timestamp; const periodFinish = bn(now.toString()).add(await time.duration.days(30)); expect( await staking.periodFinish(), ).to.be.a.bignumber.equal(periodFinish); await staking.updatePeriodFinish(now.toString()); expect(await staking.periodFinish()).to.be.a.bignumber.equal(now.toString()); }); }); describe('totalStaked', () => { it('should return totalStaked', async () => { expect(await staking.totalStaked()).to.be.a.bignumber.equal('0'); await staking.stake(ether('10'), {from: wallet1}); expect(await staking.totalStaked()).to.be.a.bignumber.equal( ether('10'), ); await staking.stake(ether('20'), {from: wallet2}); expect(await staking.totalStaked()).to.be.a.bignumber.equal( ether('30'), ); }); }); describe('balanceOf', () => { it('should return staked amount', async () => { expect(await staking.balanceOf(wallet1)).to.be.a.bignumber.equal('0'); await staking.stake(ether('15'), {from: wallet1}); await staking.stake(ether('5'), {from: wallet2}); expect(await staking.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('15'), ); expect(await staking.balanceOf(wallet2)).to.be.a.bignumber.equal( ether('5'), ); await staking.stake(ether('10'), {from: wallet1}); expect(await staking.balanceOf(wallet1)).to.be.a.bignumber.equal( ether('25'), ); }); }); describe('getRewardForDuration', () => { it('should return reward for duration', async () => { expect(await staking.getRewardForDuration()).to.be.a.bignumber.equal( '0', ); await xft.mint(staking.address, ether('10000'), {from: owner}); await staking.notifyRewardAmount(ether('10000'), {from: owner}); expect( await staking.getRewardForDuration(), ).to.be.a.bignumber.that.is.at.closeTo( ether('10000'), ether('0.00001'), ); }); }); }); }, );