Skip to content

Commit

Permalink
Add some MasterChef tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chefnomi committed Aug 24, 2020
1 parent 086dfb1 commit eee6ead
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 42 deletions.
22 changes: 16 additions & 6 deletions contracts/MasterChef.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "./SushiToken.sol";
interface Migrator {
// Perform LP token migration from legacy UniswapV2 to SushiSwap.
// Take ERC20 token address and return the new LP token address.
// Migrator should have full access to the caller's LP token.
// Migrator should have full access to the caller's LP token.
// Return the new LP token address and new `howmany`.
//
// XXX Migrator must have allowance access to UniswapV2 LP tokens.
Expand Down Expand Up @@ -46,7 +46,7 @@ contract MasterChef is Ownable {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
//
// We do some fancy math here. Basically, any point in time, the amount of SUSHIs
// We do some fancy math here. Basically, any point in time, the amount of SUSHIs
// entitled to a user but is pending to be distributed is:
//
// pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
Expand Down Expand Up @@ -121,7 +121,7 @@ contract MasterChef is Ownable {
_pool.lastRewardBlock = block.number;
return;
}
uint256 blockCount = _pool.lastRewardBlock.sub(block.number);
uint256 blockCount = block.number.sub(_pool.lastRewardBlock);
uint256 sushiReward = blockCount.mul(_pool.howmany);
sushi.mint(devaddr, sushiReward.mul(devshare).div(1e9));
sushi.mint(address(this), sushiReward);
Expand All @@ -137,7 +137,7 @@ contract MasterChef is Ownable {
updatePool(pool);
if (user.amount > 0) {
uint256 pending = user.amount.mul(pool.accSushiPerShare).div(1e9).sub(user.rewardDebt);
sushi.transfer(msg.sender, pending);
safeSushiTransfer(msg.sender, pending);
}
pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
user.amount = user.amount.add(_amount);
Expand All @@ -149,10 +149,10 @@ contract MasterChef is Ownable {
PoolInfo storage pool = poolInfo[_token];
UserInfo storage user = userInfo[_token][msg.sender];
require(pool.lpToken != IERC20(address(0)), "withdraw: wut?");
require(user.amount > _amount, "withdraw: not good");
require(user.amount >= _amount, "withdraw: not good");
updatePool(pool);
uint256 pending = user.amount.mul(pool.accSushiPerShare).div(1e9).sub(user.rewardDebt);
sushi.transfer(msg.sender, pending);
safeSushiTransfer(msg.sender, pending);
user.amount = user.amount.sub(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e9);
pool.lpToken.safeTransfer(address(msg.sender), _amount);
Expand All @@ -168,6 +168,16 @@ contract MasterChef is Ownable {
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
}

// Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.
function safeSushiTransfer(address _to, uint256 _amount) internal {
uint256 sushiBal = sushi.balanceOf(address(this));
if (_amount > sushiBal) {
sushi.transfer(_to, sushiBal);
} else {
sushi.transfer(_to, _amount);
}
}

// Update dev address by the previous dev.
function dev(address _devaddr) public {
require(msg.sender == devaddr, "dev: wut?");
Expand Down
140 changes: 140 additions & 0 deletions test/MasterChef.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const { expectRevert, time } = require('@openzeppelin/test-helpers');
const SushiToken = artifacts.require('SushiToken');
const MasterChef = artifacts.require('MasterChef');
const MockERC20 = artifacts.require('MockERC20');

contract('SushiToken', ([alice, bob, carol, dev, minter]) => {
beforeEach(async () => {
this.sushi = await SushiToken.new({ from: alice });
this.chef = await MasterChef.new(this.sushi.address, dev, { from: alice });
await this.sushi.transferOwnership(this.chef.address, { from: alice });
});

it('should set correct state variables', async () => {
const sushi = await this.chef.sushi();
const devaddr = await this.chef.devaddr();
const owner = await this.sushi.owner();
assert.equal(sushi.valueOf(), this.sushi.address);
assert.equal(devaddr.valueOf(), dev);
assert.equal(owner.valueOf(), this.chef.address);
});

context('With ERC/LP token added to the field', () => {
beforeEach(async () => {
this.token = await MockERC20.new('Token', 'TOKEN', '10000000000', {
from: minter,
});
this.lp = await MockERC20.new('LPToken', 'LP', '10000000000', {
from: minter,
});
await this.lp.transfer(alice, '1000', { from: minter });
await this.lp.transfer(bob, '1000', { from: minter });
await this.lp.transfer(carol, '1000', { from: minter });
});

it('should give out SUSHIs only after farming time', async () => {
// 1000 per block farming rate starting at block 100
await this.chef.add(this.token.address, '1000', this.lp.address, '100');
await this.lp.approve(this.chef.address, '1000', { from: bob });
await this.chef.deposit(this.token.address, '100', { from: bob });
await time.advanceBlockTo('89');
await this.chef.deposit(this.token.address, '0', { from: bob }); // block 90
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0');
await time.advanceBlockTo('94');
await this.chef.deposit(this.token.address, '0', { from: bob }); // block 95
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0');
await time.advanceBlockTo('99');
await this.chef.deposit(this.token.address, '0', { from: bob }); // block 100
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0');
await time.advanceBlockTo('100');
await this.chef.deposit(this.token.address, '0', { from: bob }); // block 101
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '1000');
await time.advanceBlockTo('104');
await this.chef.deposit(this.token.address, '0', { from: bob }); // block 105
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '5000');
assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '500');
assert.equal((await this.sushi.totalSupply()).valueOf(), '5500');
});

it('should not distribute SUSHIs if no one deposit', async () => {
// 1000 per block farming rate starting at block 200
await this.chef.add(this.token.address, '1000', this.lp.address, '100');
await this.lp.approve(this.chef.address, '1000', { from: bob });
await time.advanceBlockTo('199');
assert.equal((await this.sushi.totalSupply()).valueOf(), '0');
await time.advanceBlockTo('204');
assert.equal((await this.sushi.totalSupply()).valueOf(), '0');
await time.advanceBlockTo('209');
await this.chef.deposit(this.token.address, '10', { from: bob }); // block 210
assert.equal((await this.sushi.totalSupply()).valueOf(), '0');
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0');
assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '0');
assert.equal((await this.lp.balanceOf(bob)).valueOf(), '990');
await time.advanceBlockTo('219');
await this.chef.withdraw(this.token.address, '10', { from: bob }); // block 220
assert.equal((await this.sushi.totalSupply()).valueOf(), '11000');
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '10000');
assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '1000');
assert.equal((await this.lp.balanceOf(bob)).valueOf(), '1000');
});

it('should distribute SUSHIs properly', async () => {
// 1000 per block farming rate starting at block 300
await this.chef.add(this.token.address, '1000', this.lp.address, '300');
await this.lp.approve(this.chef.address, '1000', { from: alice });
await this.lp.approve(this.chef.address, '1000', { from: bob });
await this.lp.approve(this.chef.address, '1000', { from: carol });
// Alice deposits 10 LPs at block 310
await time.advanceBlockTo('309');
await this.chef.deposit(this.token.address, '10', { from: alice });
// Bob deposits 20 LPs at block 314
await time.advanceBlockTo('313');
await this.chef.deposit(this.token.address, '20', { from: bob });
// Carol deposits 30 LPs at block 318
await time.advanceBlockTo('317');
await this.chef.deposit(this.token.address, '30', { from: carol });
// Alice deposits 10 more LPs at block 320. At this point:
// Alice should have: 4*1000 + 4*1/3*1000 + 2*1/6*1000 = 5666
// MasterChef should have the remaining: 10000 - 5666 = 4334
await time.advanceBlockTo('319')
await this.chef.deposit(this.token.address, '10', { from: alice });
assert.equal((await this.sushi.totalSupply()).valueOf(), '11000');
assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '5666');
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0');
assert.equal((await this.sushi.balanceOf(carol)).valueOf(), '0');
assert.equal((await this.sushi.balanceOf(this.chef.address)).valueOf(), '4334');
assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '1000');
// Bob withdraws 5 LPs at block 330. At this point:
// Bob should have: 4*2/3*1000 + 2*2/6*1000 + 10*2/7*1000 = 6190
await time.advanceBlockTo('329')
await this.chef.withdraw(this.token.address, '5', { from: bob });
assert.equal((await this.sushi.totalSupply()).valueOf(), '22000');
assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '5666');
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '6190');
assert.equal((await this.sushi.balanceOf(carol)).valueOf(), '0');
assert.equal((await this.sushi.balanceOf(this.chef.address)).valueOf(), '8144');
assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '2000');
// Alice withdraws 20 LPs at block 340.
// Bob withdraws 15 LPs at block 350.
// Carol withdraws 30 LPs at block 360.
await time.advanceBlockTo('339')
await this.chef.withdraw(this.token.address, '20', { from: alice });
await time.advanceBlockTo('349')
await this.chef.withdraw(this.token.address, '15', { from: bob });
await time.advanceBlockTo('359')
await this.chef.withdraw(this.token.address, '30', { from: carol });
assert.equal((await this.sushi.totalSupply()).valueOf(), '55000');
assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '5000');
// Alice should have: 5666 + 10*2/7*1000 + 10*2/6.5*1000 = 11600
assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '11600');
// Bob should have: 6190 + 10*1.5/6.5 * 1000 + 10*1.5/4.5*1000 = 11831
assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '11831');
// Carol should have: 2*3/6*1000 + 10*3/7*1000 + 10*3/6.5*1000 + 10*3/4.5*1000 + 10*1000 = 26568
assert.equal((await this.sushi.balanceOf(carol)).valueOf(), '26568');
// All of them should have 1000 LPs back.
assert.equal((await this.lp.balanceOf(alice)).valueOf(), '1000');
assert.equal((await this.lp.balanceOf(bob)).valueOf(), '1000');
assert.equal((await this.lp.balanceOf(carol)).valueOf(), '1000');
});
});
});
71 changes: 35 additions & 36 deletions test/SushiToken.test.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,61 @@
const { expectRevert } = require("@openzeppelin/test-helpers");
const SushiToken = artifacts.require("SushiToken");
const { expectRevert } = require('@openzeppelin/test-helpers');
const SushiToken = artifacts.require('SushiToken');

contract('SushiToken', ([alice, bob, carol]) => {
beforeEach(async () => {
this.sushi = await SushiToken.new({ from: alice });
});

it('should have correct name and symbol and decimal', async () => {
const name = await this.sushi.name.call();
const symbol = await this.sushi.symbol.call();
const decimals = await this.sushi.decimals.call();
assert.equal(name.valueOf(), "SushiToken");
assert.equal(symbol.valueOf(), "SUSHI");
assert.equal(decimals.valueOf(), "18");
const name = await this.sushi.name();
const symbol = await this.sushi.symbol();
const decimals = await this.sushi.decimals();
assert.equal(name.valueOf(), 'SushiToken');
assert.equal(symbol.valueOf(), 'SUSHI');
assert.equal(decimals.valueOf(), '18');
});

it('should only allow owner to mint token', async () => {
await this.sushi.mint(alice, "100", { from: alice });
await this.sushi.mint(bob, "1000", { from: alice });
await this.sushi.mint(alice, '100', { from: alice });
await this.sushi.mint(bob, '1000', { from: alice });
await expectRevert(
this.sushi.mint(carol, "1000", { from: bob }),
"Ownable: caller is not the owner",
this.sushi.mint(carol, '1000', { from: bob }),
'Ownable: caller is not the owner',
);
const totalSupply = await this.sushi.totalSupply();
const aliceBal = await this.sushi.balanceOf.call(alice);
const bobBal = await this.sushi.balanceOf.call(bob);
const carolBal = await this.sushi.balanceOf.call(carol);
assert.equal(totalSupply.valueOf(), "1100");
assert.equal(aliceBal.valueOf(), "100");
assert.equal(bobBal.valueOf(), "1000");
assert.equal(carolBal.valueOf(), "0");
const aliceBal = await this.sushi.balanceOf(alice);
const bobBal = await this.sushi.balanceOf(bob);
const carolBal = await this.sushi.balanceOf(carol);
assert.equal(totalSupply.valueOf(), '1100');
assert.equal(aliceBal.valueOf(), '100');
assert.equal(bobBal.valueOf(), '1000');
assert.equal(carolBal.valueOf(), '0');
});

it('should supply token transfers properly', async () => {
await this.sushi.mint(alice, "100", { from: alice });
await this.sushi.mint(bob, "1000", { from: alice });
await this.sushi.transfer(carol, "10", { from: alice });
await this.sushi.transfer(carol, "100", { from: bob });
await this.sushi.mint(alice, '100', { from: alice });
await this.sushi.mint(bob, '1000', { from: alice });
await this.sushi.transfer(carol, '10', { from: alice });
await this.sushi.transfer(carol, '100', { from: bob });
const totalSupply = await this.sushi.totalSupply();
const aliceBal = await this.sushi.balanceOf.call(alice);
const bobBal = await this.sushi.balanceOf.call(bob);
const carolBal = await this.sushi.balanceOf.call(carol);
assert.equal(totalSupply.valueOf(), "1100");
assert.equal(aliceBal.valueOf(), "90");
assert.equal(bobBal.valueOf(), "900");
assert.equal(carolBal.valueOf(), "110");
const aliceBal = await this.sushi.balanceOf(alice);
const bobBal = await this.sushi.balanceOf(bob);
const carolBal = await this.sushi.balanceOf(carol);
assert.equal(totalSupply.valueOf(), '1100');
assert.equal(aliceBal.valueOf(), '90');
assert.equal(bobBal.valueOf(), '900');
assert.equal(carolBal.valueOf(), '110');
});

it('should fail if you try to do bad transfers', async () => {
await this.sushi.mint(alice, "100", { from: alice });
await this.sushi.mint(alice, '100', { from: alice });
await expectRevert(
this.sushi.transfer(carol, "110", { from: alice }),
"ERC20: transfer amount exceeds balance",
this.sushi.transfer(carol, '110', { from: alice }),
'ERC20: transfer amount exceeds balance',
);
await expectRevert(
this.sushi.transfer(carol, "1", { from: bob }),
"ERC20: transfer amount exceeds balance",
this.sushi.transfer(carol, '1', { from: bob }),
'ERC20: transfer amount exceeds balance',
);
});
});

0 comments on commit eee6ead

Please sign in to comment.