@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
229 lines (190 loc) • 9.43 kB
text/typescript
import { FakeContract, MockContract, MockContractFactory, smock } from '@defi-wonderland/smock';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
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 {
ERC20ForTest,
ERC20ForTest__factory,
IKeep3rV1,
IKeep3rV1Proxy,
IUniswapV3Pool,
Keep3rHelper,
Keep3rKeeperDisputableForTest,
Keep3rKeeperDisputableForTest__factory,
} from '@types';
import { wallet } from '@utils';
import { onlySlasher } from '@utils/behaviours';
import { toUnit } from '@utils/bn';
import chai, { expect } from 'chai';
import { ethers } from 'hardhat';
chai.use(smock.matchers);
describe('Keep3rKeeperDisputable', () => {
const randomKeeper = wallet.generateRandomAddress();
let governance: SignerWithAddress;
let slasher: SignerWithAddress;
let disputer: SignerWithAddress;
let keeperDisputable: MockContract<Keep3rKeeperDisputableForTest>;
let helper: FakeContract<Keep3rHelper>;
let keep3rV1: FakeContract<IKeep3rV1>;
let keep3rV1Proxy: FakeContract<IKeep3rV1Proxy>;
let oraclePool: FakeContract<IUniswapV3Pool>;
let keeperDisputableFactory: MockContractFactory<Keep3rKeeperDisputableForTest__factory>;
before(async () => {
[] = await ethers.getSigners();
keeperDisputableFactory = await smock.mock<Keep3rKeeperDisputableForTest__factory>('Keep3rKeeperDisputableForTest');
});
beforeEach(async () => {
helper = await smock.fake(IKeep3rHelperArtifact);
keep3rV1 = await smock.fake(IKeep3rV1Artifact);
keep3rV1Proxy = await smock.fake(IKeep3rV1ProxyArtifact);
oraclePool = await smock.fake(IUniswapV3PoolArtifact);
oraclePool.token0.returns(keep3rV1.address);
keeperDisputable = await keeperDisputableFactory.deploy(helper.address, keep3rV1.address, keep3rV1Proxy.address, oraclePool.address);
await keeperDisputable.setVariable('slashers', { [slasher.address]: true });
await keeperDisputable.setVariable('disputers', { [disputer.address]: true });
});
describe('slash', () => {
const bondAmount = toUnit(3);
const unbondAmount = toUnit(5);
onlySlasher(
() => keeperDisputable,
'slash',
[],
() => [randomKeeper, keeperDisputable.address, 1, 1]
);
beforeEach(async () => {
keep3rV1.transfer.returns(true);
keep3rV1.transferFrom.returns(true);
await keeperDisputable.setVariable('bonds', {
[]: { [keep3rV1.address]: bondAmount },
});
await keeperDisputable.setVariable('pendingUnbonds', {
[]: { [keep3rV1.address]: unbondAmount },
});
await keeperDisputable.connect(disputer).dispute(randomKeeper);
});
it('should revert if keeper is not disputed', async () => {
const undisputedKeeper = wallet.generateRandomAddress();
await expect(keeperDisputable.connect(slasher).slash(undisputedKeeper, keep3rV1.address, toUnit(0.1), toUnit(0.1))).to.be.revertedWith(
'NotDisputed()'
);
});
it('should emit event', async () => {
await expect(keeperDisputable.connect(slasher).slash(randomKeeper, keep3rV1.address, toUnit(0.1), toUnit(0.2)))
.to.emit(keeperDisputable, 'KeeperSlash')
.withArgs(randomKeeper, slasher.address, toUnit(0.1).add(toUnit(0.2)));
});
it('should slash specified bond amount', async () => {
const slashBondAmount = toUnit(1);
await keeperDisputable.connect(slasher).slash(randomKeeper, keep3rV1.address, slashBondAmount, 0);
expect(await keeperDisputable.bonds(randomKeeper, keep3rV1.address)).to.equal(bondAmount.sub(slashBondAmount));
});
it('should slash specified unbond amount', async () => {
const slashUnbondAmount = toUnit(1);
await keeperDisputable.connect(slasher).slash(randomKeeper, keep3rV1.address, 0, slashUnbondAmount);
expect(await keeperDisputable.pendingUnbonds(randomKeeper, keep3rV1.address)).to.equal(unbondAmount.sub(slashUnbondAmount));
});
});
describe('revoke', () => {
onlySlasher(() => keeperDisputable, 'revoke', [slasher], [randomKeeper]);
beforeEach(async () => {
await keeperDisputable.setKeeper(randomKeeper);
});
it('should revert if keeper was not disputed', async () => {
await expect(keeperDisputable.connect(slasher).revoke(randomKeeper)).to.be.revertedWith('NotDisputed()');
});
context('when keeper was disputed', () => {
beforeEach(async () => {
await keeperDisputable.connect(disputer).dispute(randomKeeper);
});
it('should remove keeper', async () => {
await keeperDisputable.connect(slasher).revoke(randomKeeper);
expect(await keeperDisputable.isKeeper(randomKeeper)).to.equal(false);
});
it('should keep keeper disputed', async () => {
await keeperDisputable.connect(slasher).revoke(randomKeeper);
expect(await keeperDisputable.disputes(randomKeeper)).to.equal(true);
});
it('should emit event', async () => {
await expect(keeperDisputable.connect(slasher).revoke(randomKeeper))
.to.emit(keeperDisputable, 'KeeperRevoke')
.withArgs(randomKeeper, slasher.address);
});
it('should slash all keeper KP3R bonds', async () => {
await keeperDisputable.setVariable('bonds', {
[]: { [keep3rV1.address]: toUnit(1) },
});
await keeperDisputable.connect(slasher).revoke(randomKeeper);
expect(await keeperDisputable.bonds(randomKeeper, keep3rV1.address)).to.equal(toUnit(0));
});
});
});
describe('internal slash', () => {
context('when using an ERC20 bond', () => {
const bondAmount = toUnit(2);
const unbondAmount = toUnit(5);
let erc20Factory: MockContractFactory<ERC20ForTest__factory>;
let erc20: MockContract<ERC20ForTest>;
before(async () => {
erc20Factory = await smock.mock<ERC20ForTest__factory>('ERC20ForTest');
});
beforeEach(async () => {
erc20 = await erc20Factory.deploy('Sample', 'SMP', keeperDisputable.address, toUnit(2));
await keeperDisputable.setVariable('bonds', {
[]: { [erc20.address]: bondAmount },
});
await keeperDisputable.setVariable('pendingUnbonds', {
[]: { [erc20.address]: unbondAmount },
});
erc20.transfer.returns(true);
});
it('should not revert if transfer fails', async () => {
erc20.transfer.reverts();
await expect(keeperDisputable.internalSlash(randomKeeper, erc20.address, toUnit(1), toUnit(1))).not.to.be.reverted;
});
it('should transfer both bond and pending unbond tokens to governance', async () => {
await keeperDisputable.internalSlash(randomKeeper, erc20.address, bondAmount, unbondAmount);
expect(erc20.transfer).to.be.calledOnceWith(governance.address, bondAmount.add(unbondAmount));
});
it('should reduce keeper bonds', async () => {
const slashBondAmount = toUnit(1);
await keeperDisputable.internalSlash(randomKeeper, erc20.address, slashBondAmount, 0);
expect(await keeperDisputable.bonds(randomKeeper, erc20.address)).to.equal(bondAmount.sub(slashBondAmount));
});
it('should reduce keeper pending unbonds', async () => {
const slashUnbondAmount = toUnit(1);
await keeperDisputable.internalSlash(randomKeeper, erc20.address, 0, slashUnbondAmount);
expect(await keeperDisputable.pendingUnbonds(randomKeeper, erc20.address)).to.equal(unbondAmount.sub(slashUnbondAmount));
});
});
context('when using a KP3R bond', () => {
const bondAmount = toUnit(2);
const unbondAmount = toUnit(5);
beforeEach(async () => {
await keeperDisputable.setVariable('bonds', {
[]: { [keep3rV1.address]: bondAmount },
});
await keeperDisputable.setVariable('pendingUnbonds', {
[]: { [keep3rV1.address]: unbondAmount },
});
});
it('should reduce keeper bonds', async () => {
const slashBondAmount = toUnit(1);
await keeperDisputable.internalSlash(randomKeeper, keep3rV1.address, slashBondAmount, 0);
expect(await keeperDisputable.bonds(randomKeeper, keep3rV1.address)).to.equal(bondAmount.sub(slashBondAmount));
});
it('should reduce keeper pending unbonds', async () => {
const slashUnbondAmount = toUnit(1);
await keeperDisputable.internalSlash(randomKeeper, keep3rV1.address, 0, slashUnbondAmount);
expect(await keeperDisputable.pendingUnbonds(randomKeeper, keep3rV1.address)).to.equal(unbondAmount.sub(slashUnbondAmount));
});
});
it('should not remove the dispute from the keeper', async () => {
await keeperDisputable.connect(disputer).dispute(randomKeeper);
await keeperDisputable.internalSlash(randomKeeper, keep3rV1.address, 0, 0);
expect(await keeperDisputable.disputes(randomKeeper)).to.equal(true);
});
});
});