Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

skimmer #457

Merged
merged 3 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions contracts/pcv/utils/FeiSkimmer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

import "../IPCVDeposit.sol";
import "../../refs/CoreRef.sol";

/// @title a contract to skim excess FEI from addresses
/// @author Fei Protocol
contract FeiSkimmer is CoreRef {

event ThresholdUpdate(uint256 newThreshold);

/// @notice source PCV deposit to skim excess FEI from
IPCVDeposit public immutable source;

/// @notice the threshold of FEI above which to skim
uint256 public threshold;

/// @notice FEI Skimmer
/// @param _core Fei Core for reference
/// @param _source the target to skim from
/// @param _threshold the threshold of FEI to be maintained by source
constructor(
address _core,
IPCVDeposit _source,
uint256 _threshold
)
CoreRef(_core)
{
source = _source;
threshold = _threshold;
emit ThresholdUpdate(threshold);
}

/// @return true if FEI balance of source exceeds threshold
function skimEligible() external view returns (bool) {
return fei().balanceOf(address(source)) > threshold;
}

/// @notice skim FEI above the threshold from the source. Pausable. Requires skimEligible()
function skim()
external
whenNotPaused
{
IFei _fei = fei();
uint256 feiTotal = _fei.balanceOf(address(source));

require(feiTotal > threshold, "under threshold");

uint256 burnAmount = feiTotal - threshold;
source.withdrawERC20(address(_fei), address(this), burnAmount);

_fei.burn(burnAmount);
}

/// @notice set the threshold for FEI skims. Only Governor or Admin
/// @param newThreshold the new value above which FEI is skimmed.
function setThreshold(uint256 newThreshold) external onlyGovernorOrAdmin {
threshold = newThreshold;
emit ThresholdUpdate(newThreshold);
}
}
83 changes: 83 additions & 0 deletions test/unit/pcv/FeiSkimmer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Core, FeiSkimmer, MockPCVDepositV2 } from '@custom-types/contracts';
import { expectRevert, getAddresses, getCore, getImpersonatedSigner } from '@test/helpers';
import { expect } from 'chai';
import { Signer } from 'ethers';
import { ethers } from 'hardhat';

describe.only('FeiSkimmer', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove .only

let minterAddress: string;
let userAddress: string;
let governorAddress: string;
let core: Core;
let skimmer: FeiSkimmer;
let source: MockPCVDepositV2;

const threshold = ethers.constants.WeiPerEther;

const impersonatedSigners: { [key: string]: Signer } = {};

before(async () => {
const addresses = await getAddresses();

// add any addresses you want to impersonate here
const impersonatedAddresses = [addresses.userAddress, addresses.governorAddress, addresses.minterAddress];

for (const address of impersonatedAddresses) {
impersonatedSigners[address] = await getImpersonatedSigner(address);
}
});

beforeEach(async function () {
({ userAddress, governorAddress, minterAddress } = await getAddresses());
core = await getCore();

source = await (await ethers.getContractFactory('MockPCVDepositV2')).deploy(core.address, await core.fei(), 0, 0);

skimmer = await (await ethers.getContractFactory('FeiSkimmer')).deploy(core.address, source.address, threshold);
});

describe('Initial configuration', function () {
it('has source set', async function () {
expect(await skimmer.source()).to.be.equal(source.address);
});

it('has threshold set', async function () {
expect(await skimmer.threshold()).to.be.equal(threshold);
});

it('is not skim eligible', async function () {
expect(await skimmer.skimEligible()).to.be.false;
});
});

describe('Skim', function () {
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
it('is eligible and functional over threshold', async function () {
const fei = await ethers.getContractAt('IFei', await core.fei());

await fei.connect(impersonatedSigners[minterAddress]).mint(source.address, ethers.constants.WeiPerEther.mul(2));

expect(await skimmer.skimEligible()).to.be.true;

await skimmer.skim();

expect(await fei.balanceOf(source.address)).to.be.equal(threshold);
});
});

describe('Set Threshold', function () {
it('from governor succeeds', async function () {
expect(await skimmer.threshold()).to.be.equal(threshold);

await skimmer.connect(impersonatedSigners[governorAddress]).setThreshold(0);

expect(await skimmer.threshold()).to.be.equal(0);
});

it('not from governor succeeds', async function () {
await expectRevert(
skimmer.connect(impersonatedSigners[userAddress]).setThreshold(0),
'CoreRef: Caller is not a governor or contract admin'
);
});
});
});