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'),
          );
        });
      });
    });
  },
);