@defi-wonderland/keep3r-v2
Version:
The Keep3r Network is a decentralized network for projects that need external devops, and for external teams to find keeper jobs
249 lines (205 loc) • 11.6 kB
text/typescript
import { FakeContract, MockContract, MockContractFactory, smock } from '@defi-wonderland/smock';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import ERC20Artifact from '@openzeppelin/contracts/build/contracts/ERC20.json';
import IUniswapV3PoolArtifact from '@solidity/for-test/IUniswapV3PoolForTest.sol/IUniswapV3PoolForTest.json';
import IKeep3rV1Artifact from '@solidity/interfaces/external/IKeep3rV1.sol/IKeep3rV1.json';
import IKeep3rV1ProxyArtifact from '@solidity/interfaces/external/IKeep3rV1Proxy.sol/IKeep3rV1Proxy.json';
import IKeep3rHelperArtifact from '@solidity/interfaces/IKeep3rHelper.sol/IKeep3rHelper.json';
import {
ERC20,
IKeep3rV1,
IKeep3rV1Proxy,
IUniswapV3Pool,
Keep3rHelper,
Keep3rKeeperFundableForTest,
Keep3rKeeperFundableForTest__factory,
} from '@types';
import { toUnit } from '@utils/bn';
import chai, { expect } from 'chai';
import { ethers } from 'hardhat';
chai.use(smock.matchers);
describe('Keep3rKeeperFundable', () => {
let randomKeeper: SignerWithAddress;
let keeperFundable: MockContract<Keep3rKeeperFundableForTest>;
let helper: FakeContract<Keep3rHelper>;
let keep3rV1: FakeContract<IKeep3rV1>;
let keeperFundableFactory: MockContractFactory<Keep3rKeeperFundableForTest__factory>;
let keep3rV1Proxy: FakeContract<IKeep3rV1Proxy>;
let erc20: FakeContract<ERC20>;
let oraclePool: FakeContract<IUniswapV3Pool>;
before(async () => {
[, randomKeeper] = await ethers.getSigners();
keeperFundableFactory = await smock.mock<Keep3rKeeperFundableForTest__factory>('Keep3rKeeperFundableForTest');
});
beforeEach(async () => {
helper = await smock.fake(IKeep3rHelperArtifact);
erc20 = await smock.fake(ERC20Artifact);
keep3rV1 = await smock.fake(IKeep3rV1Artifact);
keep3rV1Proxy = await smock.fake(IKeep3rV1ProxyArtifact);
oraclePool = await smock.fake(IUniswapV3PoolArtifact);
oraclePool.token0.returns(keep3rV1.address);
keeperFundable = await keeperFundableFactory.deploy(helper.address, keep3rV1.address, keep3rV1Proxy.address, oraclePool.address);
});
describe('bond', () => {
beforeEach(async () => {
erc20.transferFrom.returns(true);
});
it('should revert if keeper is disputed', async () => {
await keeperFundable.setVariable('disputes', { [randomKeeper.address]: true });
await expect(keeperFundable.connect(randomKeeper).bond(erc20.address, toUnit(1))).to.be.revertedWith('Disputed()');
});
it('should revert if caller is already a job', async () => {
await keeperFundable.setJob(randomKeeper.address);
await expect(keeperFundable.connect(randomKeeper).bond(erc20.address, toUnit(1))).to.be.revertedWith('AlreadyAJob()');
});
it('should emit event', async () => {
const bondingAmount = toUnit(1);
erc20.balanceOf.returnsAtCall(0, toUnit(0));
erc20.balanceOf.returnsAtCall(1, toUnit(1));
await expect(keeperFundable.connect(randomKeeper).bond(erc20.address, bondingAmount))
.to.emit(keeperFundable, 'Bonding')
.withArgs(randomKeeper.address, erc20.address, bondingAmount);
});
});
describe('unbond', () => {
const bondedAmount = toUnit(1);
beforeEach(async () => {
await keeperFundable.setVariable('bonds', {
[randomKeeper.address]: {
[erc20.address]: bondedAmount,
[keep3rV1.address]: bondedAmount,
},
});
});
it('should register the unblock timestamp', async () => {
const unbondTime = await keeperFundable.callStatic.unbondTime();
await keeperFundable.connect(randomKeeper).unbond(erc20.address, toUnit(1));
const unbondBlockTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
const canWithdrawAfter = await keeperFundable.canWithdrawAfter(randomKeeper.address, erc20.address);
expect(canWithdrawAfter).to.equal(unbondTime.add(unbondBlockTimestamp));
});
it('should reduce amount from bonds', async () => {
const toUnbondAmount = toUnit(0.1);
await keeperFundable.connect(randomKeeper).unbond(erc20.address, toUnbondAmount);
const remaining = await keeperFundable.bonds(randomKeeper.address, erc20.address);
expect(remaining).to.equal(bondedAmount.sub(toUnbondAmount));
});
it('should add to the current pending unbond amount', async () => {
const initialpendingUnbonds = toUnit(2);
const toUnbondAmount = toUnit(1);
await keeperFundable.setVariable('pendingUnbonds', {
[randomKeeper.address]: {
[erc20.address]: initialpendingUnbonds,
},
});
await keeperFundable.connect(randomKeeper).unbond(erc20.address, toUnbondAmount);
const pendingUnbonds = await keeperFundable.pendingUnbonds(randomKeeper.address, erc20.address);
expect(pendingUnbonds).to.equal(initialpendingUnbonds.add(toUnbondAmount));
});
it('should emit event', async () => {
const unbondingAmount = toUnit(0.1);
const tx = await keeperFundable.connect(randomKeeper).unbond(erc20.address, unbondingAmount);
await expect(tx).to.emit(keeperFundable, 'Unbonding').withArgs(randomKeeper.address, erc20.address, unbondingAmount);
});
});
describe('activate', () => {
it('should revert to a disputed keeper', async () => {
await keeperFundable.setVariable('disputes', { [randomKeeper.address]: true });
await expect(keeperFundable.connect(randomKeeper).activate(erc20.address)).to.be.revertedWith('Disputed');
});
it('should revert if bondings are unexistent', async () => {
await expect(keeperFundable.connect(randomKeeper).activate(erc20.address)).to.be.revertedWith('BondsUnexistent');
});
it('should revert if bondings are blocked', async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('canActivateAfter', { [randomKeeper.address]: { [erc20.address]: lastBlock.timestamp + 10 } });
await expect(keeperFundable.connect(randomKeeper).activate(erc20.address)).to.be.revertedWith('BondsLocked');
});
context('when activating any ERC20', () => {
beforeEach(async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('canActivateAfter', { [randomKeeper.address]: { [erc20.address]: lastBlock.timestamp } });
await keeperFundable.setVariable('pendingBonds', { [randomKeeper.address]: { [erc20.address]: toUnit(1) } });
});
it('should add the keeper', async () => {
await keeperFundable.connect(randomKeeper).activate(erc20.address);
expect(await keeperFundable.isKeeper(randomKeeper.address)).to.be.true;
});
it('should reset pending bonds for that token', async () => {
expect(await keeperFundable.pendingBonds(randomKeeper.address, erc20.address)).to.be.eq(toUnit(1));
await keeperFundable.connect(randomKeeper).activate(erc20.address);
expect(await keeperFundable.pendingBonds(randomKeeper.address, erc20.address)).to.be.eq(0);
});
it('should add pending bonds to keeper accountance', async () => {
expect(await keeperFundable.bonds(randomKeeper.address, erc20.address)).to.be.eq(0);
await keeperFundable.connect(randomKeeper).activate(erc20.address);
expect(await keeperFundable.bonds(randomKeeper.address, erc20.address)).to.be.eq(toUnit(1));
});
it('should emit event', async () => {
const tx = await keeperFundable.connect(randomKeeper).activate(erc20.address);
await expect(tx).to.emit(keeperFundable, 'Activation').withArgs(randomKeeper.address, erc20.address, toUnit(1));
});
});
context('when activating KP3R', () => {
beforeEach(async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('canActivateAfter', { [randomKeeper.address]: { [keep3rV1.address]: lastBlock.timestamp } });
await keeperFundable.setVariable('pendingBonds', { [randomKeeper.address]: { [keep3rV1.address]: toUnit(1) } });
});
it('should burn bonded KP3Rs', async () => {
await keeperFundable.connect(randomKeeper).activate(keep3rV1.address);
expect(keep3rV1.burn).to.have.been.calledWith(toUnit(1));
});
});
});
describe('withdraw', () => {
it('should revert if bondings are unexistent', async () => {
await expect(keeperFundable.connect(randomKeeper).withdraw(erc20.address)).to.be.revertedWith('UnbondsUnexistent');
});
it('should revert if bondings are blocked', async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('pendingUnbonds', { [randomKeeper.address]: { [erc20.address]: toUnit(1) } });
await keeperFundable.setVariable('canWithdrawAfter', { [randomKeeper.address]: { [erc20.address]: lastBlock.timestamp + 1000 } });
await expect(keeperFundable.connect(randomKeeper).withdraw(erc20.address)).to.be.revertedWith('UnbondsLocked');
});
it('should revert to a disputed keeper', async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('pendingUnbonds', { [randomKeeper.address]: { [erc20.address]: toUnit(1) } });
await keeperFundable.setVariable('canWithdrawAfter', { [randomKeeper.address]: { [erc20.address]: lastBlock.timestamp } });
await keeperFundable.setVariable('disputes', { [randomKeeper.address]: true });
await expect(keeperFundable.connect(randomKeeper).withdraw(erc20.address)).to.be.revertedWith('Disputed');
});
context('when withdrawing any ERC20', () => {
beforeEach(async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('canWithdrawAfter', { [randomKeeper.address]: { [erc20.address]: lastBlock.timestamp } });
await keeperFundable.setVariable('pendingUnbonds', { [randomKeeper.address]: { [erc20.address]: toUnit(1) } });
erc20.transfer.returns(true);
});
it('should transfer the unbonded amount to the keeper', async () => {
await keeperFundable.connect(randomKeeper).withdraw(erc20.address);
expect(erc20.transfer).to.have.been.calledWith(randomKeeper.address, toUnit(1));
});
it('should reset the unbondigs', async () => {
await keeperFundable.connect(randomKeeper).withdraw(erc20.address);
expect(await keeperFundable.pendingUnbonds(randomKeeper.address, erc20.address)).to.be.eq(0);
});
it('should emit event', async () => {
const tx = await keeperFundable.connect(randomKeeper).withdraw(erc20.address);
await expect(tx).to.emit(keeperFundable, 'Withdrawal').withArgs(randomKeeper.address, erc20.address, toUnit(1));
});
});
context('when withdrawing KP3R', () => {
beforeEach(async () => {
const lastBlock = await ethers.provider.getBlock('latest');
await keeperFundable.setVariable('canWithdrawAfter', { [randomKeeper.address]: { [keep3rV1.address]: lastBlock.timestamp } });
await keeperFundable.setVariable('pendingUnbonds', { [randomKeeper.address]: { [keep3rV1.address]: toUnit(1) } });
keep3rV1.transfer.returns(true);
});
it('should mint withdrawn KP3Rs', async () => {
await keeperFundable.connect(randomKeeper).withdraw(keep3rV1.address);
expect(keep3rV1Proxy['mint(uint256)']).to.have.been.calledWith(toUnit(1));
});
});
});
});