Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
72 changes: 72 additions & 0 deletions contracts/pcv/EthPCVDripper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;

import "../refs/CoreRef.sol";
import "../utils/Timed.sol";

/// @title a PCV dripper
/// @author Fei Protocol
contract EthPCVDripper is CoreRef, Timed {
using Address for address payable;

/// @notice target address to drip to
address payable public target;

/// @notice amount to drip after each window
uint256 public amountToDrip;

event Dripped(uint256 amount);
event Withdrawal(address indexed to, uint256 amount);

/// @notice ETH PCV Dripper constructor
/// @param _core Fei Core for reference
/// @param _target address to drip to
/// @param _frequency frequency of dripping
/// @param _amountToDrip amount to drip on each drip
constructor(
address _core,
address payable _target,
uint256 _frequency,
uint256 _amountToDrip
) public CoreRef(_core) Timed(_frequency) {
target = _target;
amountToDrip = _amountToDrip;

// start timer
_initTimed();
}

receive() external payable {}

/// @notice withdraw ETH from the PCV dripper
/// @param amount of tokens withdrawn
/// @param to the address to send PCV to
function withdrawETH(address payable to, uint256 amount)
external
onlyPCVController
{
to.sendValue(amount);
emit Withdrawal(to, amount);
}

/// @notice drip ETH to target
function drip()
external
afterTime
whenNotPaused
{
require(isTargetBalanceLow(), "EthPCVDripper: target balance too high");

// reset timer
_initTimed();

// drip
target.sendValue(amountToDrip);
emit Dripped(amountToDrip);
}

/// @notice checks whether the target balance is less than the drip amount
function isTargetBalanceLow() public view returns(bool) {
return target.balance < amountToDrip;
}
}
69 changes: 69 additions & 0 deletions contracts/pcv/EthReserveStabilizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;

import "./IReserveStabilizer.sol";
import "../refs/OracleRef.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/// @title implementation for an ETH Reserve Stabilizer
/// @author Fei Protocol
contract EthReserveStabilizer is OracleRef, IReserveStabilizer {

/// @notice the USD per FEI exchange rate denominated in basis points (1/10000)
uint256 public override usdPerFeiBasisPoints;

/// @notice the denominator for basis granularity (10,000)
uint256 public constant BASIS_POINTS_GRANULARITY = 10_000;

constructor(
address _core,
address _oracle,
uint _usdPerFeiBasisPoints
) public OracleRef(_core, _oracle) {
require(_usdPerFeiBasisPoints <= BASIS_POINTS_GRANULARITY, "ReserveStabilizer: Exceeds bp granularity");
usdPerFeiBasisPoints = _usdPerFeiBasisPoints;
emit UsdPerFeiRateUpdate(_usdPerFeiBasisPoints);
}

receive() external payable {}

/// @notice exchange FEI for ETH from the reserves
/// @param feiAmount of FEI to sell
function exchangeFei(uint256 feiAmount) external override whenNotPaused returns (uint256 amountOut) {
updateOracle();

fei().burnFrom(msg.sender, feiAmount);

amountOut = getAmountOut(feiAmount);

Address.sendValue(msg.sender, amountOut);
emit FeiExchange(msg.sender, feiAmount, amountOut);
}

/// @notice returns the amount out of ETH from the reserves
/// @param amountFeiIn the amount of FEI in
function getAmountOut(uint256 amountFeiIn) public view override returns(uint256) {
uint256 adjustedAmountIn = amountFeiIn * usdPerFeiBasisPoints / BASIS_POINTS_GRANULARITY;
return _getEthAmountOut(adjustedAmountIn);
}

function _getEthAmountOut(uint256 usdAmountIn) internal view returns(uint256) {
return invert(peg()).mul(usdAmountIn).asUint256();
}

/// @notice withdraw ETH from the reserves
/// @param to address to send ETH
/// @param amountOut amount of ETH to send
function withdraw(address payable to, uint256 amountOut) external override onlyPCVController {
Address.sendValue(to, amountOut);
emit Withdrawal(msg.sender, to, amountOut);
}

/// @notice sets the USD per FEI exchange rate rate
/// @param _usdPerFeiBasisPoints the USD per FEI exchange rate denominated in basis points (1/10000)
function setUsdPerFeiRate(uint256 _usdPerFeiBasisPoints) external override onlyGovernor {
require(_usdPerFeiBasisPoints <= BASIS_POINTS_GRANULARITY, "ReserveStabilizer: Exceeds bp granularity");
usdPerFeiBasisPoints = _usdPerFeiBasisPoints;
emit UsdPerFeiRateUpdate(_usdPerFeiBasisPoints);
}
}
35 changes: 35 additions & 0 deletions contracts/pcv/IReserveStabilizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity ^0.6.2;

/// @title a Reserve Stabilizer interface
/// @author Fei Protocol
interface IReserveStabilizer {

// ----------- Events -----------
event FeiExchange(address indexed to, uint256 feiAmountIn, uint256 amountOut);

event UsdPerFeiRateUpdate(uint256 basisPoints);

event Withdrawal(
address indexed _caller,
address indexed _to,
uint256 _amount
);

// ----------- State changing api -----------

function exchangeFei(uint256 feiAmount) external returns (uint256);

// ----------- PCV Controller only state changing api -----------

function withdraw(address payable to, uint256 amount) external;

// ----------- Governor only state changing api -----------

function setUsdPerFeiRate(uint256 exchangeRateBasisPoints) external;

// ----------- Getters -----------

function usdPerFeiBasisPoints() external view returns (uint256);

function getAmountOut(uint256 amountIn) external view returns (uint256);
}
70 changes: 70 additions & 0 deletions contracts/staking/TribeDripper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;

import "../refs/CoreRef.sol";
import "../utils/Timed.sol";

/// @title a Tribe dripper
/// @author Fei Protocol
contract TribeDripper is CoreRef, Timed {

/// @notice target address to drip to
address public target;

/// @notice amount to drip after each window
uint256[] public amountsToDrip;

/// @notice index of the amount to drip
uint256 public dripIndex;

event Dripped(uint256 amount, uint256 dripIndex);
event Withdrawal(address indexed to, uint256 amount);

/// @notice Tribe Dripper constructor
/// @param _core Fei Core for reference
/// @param _target target address to receive TRIBE
/// @param _frequency TRIBE drip frequency
constructor(
address _core,
address _target,
uint256 _frequency,
uint256[] memory _amountsToDrip
) public CoreRef(_core) Timed(_frequency) {
target = _target;
amountsToDrip = _amountsToDrip;
}

receive() external payable {}

/// @notice withdraw TRIBE from the Tribe dripper
/// @param amount of tokens withdrawn
/// @param to the address to send PCV to
function withdraw(address to, uint256 amount)
external
onlyPCVController
{
tribe().transfer(to, amount);
emit Withdrawal(to, amount);
}

/// @notice drip TRIBE to target
function drip()
external
whenNotPaused
{
require(!isTimeStarted() || isTimeEnded(), "TribeDripper: time not ended");

// reset timer
_initTimed();

uint256 currentDripIndex = dripIndex;
require(currentDripIndex < amountsToDrip.length, "TribeDripper: index out of bounds");

uint256 amountToDrip = amountsToDrip[currentDripIndex];
dripIndex = currentDripIndex + 1;

// drip
tribe().transfer(target, amountToDrip);
emit Dripped(amountToDrip, currentDripIndex);
}
}
27 changes: 27 additions & 0 deletions migrations/14_fip_2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const EthReserveStabilizer = artifacts.require("EthReserveStabilizer");
const EthPCVDripper = artifacts.require("EthPCVDripper");
const TribeDripper = artifacts.require("TribeDripper");

module.exports = function(deployer) {
require('dotenv').config();
const core = process.env.MAINNET_CORE;
const oracle = process.env.MAINNET_UNISWAP_ORACLE;
const rewardsDistributor = process.env.MAINNET_FEI_REWARDS_DISTRIBUTOR;
deployer.then(function() {
return deployer.deploy(EthReserveStabilizer, core, oracle, "9500");
}).then(function(instance) {
return deployer.deploy(EthPCVDripper, core, instance.address, "3600", "5000000000000000000000"); // 5000 ETH per hour
}).then(function() {
return deployer.deploy(
TribeDripper,
core,
rewardsDistributor,
"604800", // weekly distribution
[
"47000000000000000000000000",
"31000000000000000000000000",
"22000000000000000000000000"
]
);
});
}
6 changes: 6 additions & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const { expect } = chai;
const BondingCurveOracle = contract.fromArtifact('BondingCurveOracle');
const Core = contract.fromArtifact('Core');
const EthBondingCurve = contract.fromArtifact('EthBondingCurve');
const EthReserveStabilizer = contract.fromArtifact('EthReserveStabilizer');
const EthPCVDripper = contract.fromArtifact('EthPCVDripper');
const EthUniswapPCVController = contract.fromArtifact('EthUniswapPCVController');
const EthUniswapPCVDeposit = contract.fromArtifact('EthUniswapPCVDeposit');
const Fei = contract.fromArtifact('Fei');
Expand All @@ -24,6 +26,7 @@ const IDO = contract.fromArtifact('IDO');
const Roots = contract.fromArtifact('RootsWrapper');
const TimelockedDelegator = contract.fromArtifact('TimelockedDelegator');
const Tribe = contract.fromArtifact('Tribe');
const TribeDripper = contract.fromArtifact("TribeDripper");
const UniswapIncentive = contract.fromArtifact('UniswapIncentive');
const UniswapOracle = contract.fromArtifact('UniswapOracle');
const FeiStakingRewards = contract.fromArtifact('FeiStakingRewards');
Expand Down Expand Up @@ -105,6 +108,8 @@ module.exports = {
BondingCurveOracle,
Core,
EthBondingCurve,
EthReserveStabilizer,
EthPCVDripper,
EthUniswapPCVController,
EthUniswapPCVDeposit,
Fei,
Expand All @@ -117,6 +122,7 @@ module.exports = {
IDO,
TimelockedDelegator,
Tribe,
TribeDripper,
UniswapIncentive,
UniswapOracle,
// mock contracts
Expand Down
Loading