UNPKG

@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
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 () => { [governance, slasher, disputer] = 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', [slasher], () => [randomKeeper, keeperDisputable.address, 1, 1] ); beforeEach(async () => { keep3rV1.transfer.returns(true); keep3rV1.transferFrom.returns(true); await keeperDisputable.setVariable('bonds', { [randomKeeper]: { [keep3rV1.address]: bondAmount }, }); await keeperDisputable.setVariable('pendingUnbonds', { [randomKeeper]: { [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', { [randomKeeper]: { [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', { [randomKeeper]: { [erc20.address]: bondAmount }, }); await keeperDisputable.setVariable('pendingUnbonds', { [randomKeeper]: { [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', { [randomKeeper]: { [keep3rV1.address]: bondAmount }, }); await keeperDisputable.setVariable('pendingUnbonds', { [randomKeeper]: { [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); }); }); });