Skip to content

Commit

Permalink
Trim down code
Browse files Browse the repository at this point in the history
  • Loading branch information
shahthepro committed Sep 26, 2024
1 parent a20c96a commit ff606d9
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 105 deletions.
20 changes: 12 additions & 8 deletions contracts/contracts/harvest/Dripper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { IVault } from "../interfaces/IVault.sol";
*
* Design notes
* - USDT has a smaller resolution than the number of seconds
* in a week, which can make per block payouts have a rounding error. However
* in a week, which can make per second payouts have a rounding error. However
* the total effect is not large - cents per day, and this money is
* not lost, just distributed in the future. While we could use a higher
* decimal precision for the drip perBlock, we chose simpler code.
* decimal precision for the drip perSecond, we chose simpler code.
* - By calculating the changing drip rates on collects only, harvests and yield
* events don't have to call anything on this contract or pay any extra gas.
* Collect() is already be paying for a single write, since it has to reset
Expand All @@ -49,7 +49,7 @@ contract Dripper is Governable {

struct Drip {
uint64 lastCollect; // overflows 262 billion years after the sun dies
uint192 perBlock; // drip rate per block
uint192 perSecond; // drip rate per second
}

address immutable vault; // OUSD vault
Expand Down Expand Up @@ -86,7 +86,11 @@ contract Dripper is Governable {
/// @dev Change the drip duration. Governor only.
/// @param _durationSeconds the number of seconds to drip out the entire
/// balance over if no collects were called during that time.
function setDripDuration(uint256 _durationSeconds) external onlyGovernor {
function setDripDuration(uint256 _durationSeconds)
external
virtual
onlyGovernor
{
require(_durationSeconds > 0, "duration must be non-zero");
dripDuration = _durationSeconds;
_collect(); // duration change take immediate effect
Expand All @@ -113,21 +117,21 @@ contract Dripper is Governable {
returns (uint256)
{
uint256 elapsed = block.timestamp - _drip.lastCollect;
uint256 allowed = (elapsed * _drip.perBlock);
uint256 allowed = (elapsed * _drip.perSecond);
return (allowed > _balance) ? _balance : allowed;
}

/// @dev Sends the currently dripped funds to be vault, and sets
/// the new drip rate based on the new balance.
function _collect() internal {
function _collect() internal virtual {
// Calculate send
uint256 balance = IERC20(token).balanceOf(address(this));
uint256 amountToSend = _availableFunds(balance, drip);
uint256 remaining = balance - amountToSend;
// Calculate new drip perBlock
// Calculate new drip perSecond
// Gas savings by setting entire struct at one time
drip = Drip({
perBlock: uint192(remaining / dripDuration),
perSecond: uint192(remaining / dripDuration),
lastCollect: uint64(block.timestamp)
});
// Send funds
Expand Down
136 changes: 41 additions & 95 deletions contracts/contracts/harvest/FixedRateDripper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,121 +3,67 @@ pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Governable } from "../governance/Governable.sol";
import { IVault } from "../interfaces/IVault.sol";
import { Dripper } from "./Dripper.sol";

/**
* @title OUSD Dripper
* @title Fixed Rate Dripper
*
* The dripper contract smooths out the yield from point-in-time yield events
* and spreads the yield out over a configurable time period. This ensures a
* continuous per block yield to makes users happy as their next rebase
* amount is always moving up. Also, this makes historical day to day yields
* smooth, rather than going from a near zero day, to a large APY day, then
* back to a near zero day again.
*
*
* Design notes
* - USDT has a smaller resolution than the number of seconds
* in a week, which can make per block payouts have a rounding error. However
* the total effect is not large - cents per day, and this money is
* not lost, just distributed in the future. While we could use a higher
* decimal precision for the drip perBlock, we chose simpler code.
* - By calculating the changing drip rates on collects only, harvests and yield
* events don't have to call anything on this contract or pay any extra gas.
* Collect() is already be paying for a single write, since it has to reset
* the lastCollect time.
* - By having a collectAndRebase method, and having our external systems call
* that, the OUSD vault does not need any changes, not even to know the address
* of the dripper.
* - A rejected design was to retro-calculate the drip rate on each collect,
* based on the balance at the time of the collect. While this would have
* required less state, and would also have made the contract respond more quickly
* to new income, it would break the predictability that is this contract's entire
* purpose. If we did this, the amount of fundsAvailable() would make sharp increases
* when funds were deposited.
* - When the dripper recalculates the rate, it targets spending the balance over
* the duration. This means that every time that collect is called, if no
* new funds have been deposited the duration is being pushed back and the
* rate decreases. This is expected, and ends up following a smoother but
* longer curve the more collect() is called without incoming yield.
* Similar to the Dripper, Fixed Rate Dripper drips out yield per second.
* However the Strategist decides the rate and it doesn't change after
* a drip.
*
*/

contract FixedRateDripper is Governable {
contract FixedRateDripper is Dripper {
using SafeERC20 for IERC20;

address immutable vault; // OUSD vault
address immutable token; // token to drip out
uint256 public dripRate; // WETH per second
uint256 public lastCollect; // Last collect timestamp
event DripRateUpdated(uint192 oldDripRate, uint192 newDripRate);

constructor(address _vault, address _token) {
vault = _vault;
token = _token;
/**
* @dev Verifies that the caller is the Governor or Strategist.
*/
modifier onlyGovernorOrStrategist() {

Check warning on line 26 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L26

Added line #L26 was not covered by tests
require(
isGovernor() || msg.sender == IVault(vault).strategistAddr(),
"Caller is not the Strategist or Governor"
);
_;

Check warning on line 31 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L31

Added line #L31 was not covered by tests
}

/// @notice How much funds have dripped out already and are currently
// available to be sent to the vault.
/// @return The amount that would be sent if a collect was called
function availableFunds() external view returns (uint256) {
uint256 balance = IERC20(token).balanceOf(address(this));
return _availableFunds(balance);
}
constructor(address _vault, address _token) Dripper(_vault, _token) {}

Check warning on line 34 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L34

Added line #L34 was not covered by tests

/// @notice Collect all dripped funds and send to vault.
/// Recalculate new drip rate.
function collect() external {
_collect();
/// @inheritdoc Dripper
function setDripDuration(uint256) external virtual override {

Check warning on line 37 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L37

Added line #L37 was not covered by tests
// Not used in FixedRateDripper
revert("Drip duration disabled");

Check warning on line 39 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L39

Added line #L39 was not covered by tests
}

/// @notice Collect all dripped funds, send to vault, recalculate new drip
/// rate, and rebase OUSD.
function collectAndRebase() external {
_collect();
IVault(vault).rebase();
}
/// @inheritdoc Dripper
function _collect() internal virtual override {

Check warning on line 43 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L43

Added line #L43 was not covered by tests
// Calculate amount to send
uint256 balance = IERC20(token).balanceOf(address(this));
uint256 amountToSend = _availableFunds(balance, drip);

Check warning on line 46 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L45-L46

Added lines #L45 - L46 were not covered by tests

/// @dev Change the drip rate. Governor only.
/// @param _rate Amount of token to drip per second
/// balance over if no collects were called during that time.
function setDripRate(uint256 _rate) external onlyGovernor {
require(_rate > 0, "rate must be non-zero");
// Collect at current rate
_collect();
// Update the rate
dripRate = _rate;
}
// Update timestamp
drip.lastCollect = uint64(block.timestamp);

Check warning on line 49 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L49

Added line #L49 was not covered by tests

/// @dev Transfer out ERC20 tokens held by the contract. Governor only.
/// @param _asset ERC20 token address
/// @param _amount amount to transfer
function transferToken(address _asset, uint256 _amount)
external
onlyGovernor
{
IERC20(_asset).safeTransfer(governor(), _amount);
// Send funds
IERC20(token).safeTransfer(vault, amountToSend);

Check warning on line 52 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L52

Added line #L52 was not covered by tests
}

/// @dev Calculate available funds by taking the lower of either the
/// currently dripped out funds or the balance available.
/// Uses passed in parameters to calculate with for gas savings.
/// @param _balance current balance in contract
function _availableFunds(uint256 _balance) internal view returns (uint256) {
uint256 elapsed = block.timestamp - lastCollect;
uint256 allowed = (elapsed * dripRate);
return (allowed > _balance) ? _balance : allowed;
}
/**
* @dev Sets the drip rate. Callable by Strategist or Governor.
* Can be set to zero to stop dripper.
* @param _perSecond Rate of WETH to drip per second
*/
function setDripRate(uint192 _perSecond) external onlyGovernorOrStrategist {
emit DripRateUpdated(_perSecond, drip.perSecond);

Check warning on line 61 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L61

Added line #L61 was not covered by tests

/// @dev Sends the currently dripped funds to be vault, and sets
/// the new drip rate based on the new balance.
function _collect() internal {
// Calculate send
uint256 balance = IERC20(token).balanceOf(address(this));
uint256 amountToSend = _availableFunds(balance);
lastCollect = block.timestamp;
// Collect at existing rate
_collect();

Check warning on line 64 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L64

Added line #L64 was not covered by tests

// Send funds
IERC20(token).safeTransfer(vault, amountToSend);
// Update rate
drip.perSecond = _perSecond;

Check warning on line 67 in contracts/contracts/harvest/FixedRateDripper.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/harvest/FixedRateDripper.sol#L67

Added line #L67 was not covered by tests
}
}
30 changes: 30 additions & 0 deletions contracts/deploy/base/013_fixed_rate_dripper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { deployOnBaseWithGuardian } = require("../../utils/deploy-l2");
const { deployWithConfirmation } = require("../../utils/deploy");
const addresses = require("../../utils/addresses");

module.exports = deployOnBaseWithGuardian(
{
deployName: "013_fixed_rate_dripper",
},
async ({ ethers }) => {
const cOETHbDripperProxy = await ethers.getContract("OETHBaseDripperProxy");
const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy");

// Deploy new implementation
const dOETHbDripper = await deployWithConfirmation("FixedRateDripper", [
cOETHbVaultProxy.address,
addresses.base.WETH,
]);

return {
actions: [
{
// 1. Upgrade Dripper
contract: cOETHbDripperProxy,
signature: "upgradeTo(address)",
args: [dOETHbDripper.address],
},
],
};
}
);
2 changes: 1 addition & 1 deletion contracts/docs/DripperStorage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion contracts/docs/OETHDripperStorage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ff606d9

Please sign in to comment.