Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
58 changes: 58 additions & 0 deletions contracts/CErc20.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
pragma solidity ^0.5.16;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

tests for this

import "./CToken.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface CompLike {
function delegate(address delegatee) external;
}

interface RibbonMinter {
function mint(address gauge_addr) external;
}

interface RewardsDistributor {
function burn(uint256 amount) external;
}

/**
* @title Compound's CErc20 Contract
* @notice CTokens which wrap an EIP-20 underlying
* @dev This contract should not to be deployed on its own; instead, deploy `CErc20Delegator` (proxy contract) and `CErc20Delegate` (logic/implementation contract).
* @author Compound
*/
contract CErc20 is CToken, CErc20Interface {
// Minter contract for rbn gauge emissions
RibbonMinter public constant RBN_MINTER = RibbonMinter(0x5B0655F938A72052c46d2e94D206ccB6FF625A3A);
// RBN token
IERC20 public constant RBN = IERC20(0x6123b0049f904d730db3c36a31167d9d4121fa6b);
// Rewards distributor
// https://github.com/Rari-Capital/compound-protocol/blob/fuse-final/contracts/RewardsDistributorDelegator.sol
RewardsDistributor public rewardsDistributor;

/**
* @notice Initialize the new money market
* @param underlying_ The address of the underlying asset
Expand Down Expand Up @@ -178,4 +195,45 @@ contract CErc20 is CToken, CErc20Interface {
require(hasAdminRights(), "only the admin may set the comp-like delegate");
CompLike(underlying).delegate(compLikeDelegatee);
}

/**
* @notice Admin call to set rewards distributor
* @param _rewardsDistributor The rewards contract
*/
function _setRewardsDistributor(address _rewardsDistributor) external {
require(hasAdminRights(), "only the admin may set the rewards distributor delegate");
require(_rewardsDistributor != address(0), "rewards distributor must be set");

rewardsDistributor = _rewardsDistributor;
}

/**
* @notice Anyone can claim gauge rewards for collateralized gauge tokens.
*/
function claimGaugeRewards() external {
require(rewardsDistributor != address(0), "rewards distributor must be set");

// Underlying is the gauge token like rETH-THETA-gauge
RBN_MINTER.mint(underlying);

uint256 toDistribute = RBN.balanceOf(address(this));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We can add these lines to return early

if (toDistribute == 0) {
  return;
}


if (toDistribute == 0) {
return;
}

RBN.approve(rewardsDistributor, toDistribute);

/*
* Transfer rewards to reward distributor which will distribute rewards
* to those who supply / borrow. The reason we need to do this way is
* once individuals transfer the collateral (gauge tokens) to the cToken
* contract, they forfeit their rewards and now the cToken starts accumulating
* rewards. We want to redistribute some of it back to those supplying
* gauge tokens as collateral who 'should' be getting those rewards, and some
* to DAI / USDC suppliers
*/

rewardsDistributor.burn(toDistribute);
}
}
71 changes: 68 additions & 3 deletions contracts/RewardsDistributorDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,43 @@ import "./CToken.sol";
import "./ExponentialNoError.sol";
import "./Comptroller.sol";
import "./RewardsDistributorStorage.sol";
import "./SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @title RewardsDistributorDelegate (COMP distribution logic extracted from `Comptroller`)
* @author Compound
*/
contract RewardsDistributorDelegate is RewardsDistributorDelegateStorageV1, ExponentialNoError {
using SafeMath for uint;

/// @dev Notice that this contract is a RewardsDistributor
bool public constant isRewardsDistributor = true;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

tests for this

/// @dev WEEK
uint256 public constant WEEK = 604800;

/// @dev AVG blocks per week. Each block is on avg 13s
uint256 public constant AVG_BLOCKS_PER_WEEK = WEEK.div(13);

/// @dev 100%
uint256 public constant TOTAL_PCT = 10000;

/// @dev Start of rewards epoch
uint256 public startTime;

/// @dev total RBN minted from last epoch
uint256 public lastEpochTotalMint;

/// @dev total RBN minted
uint256 public totalMint;

/// @dev Borrow reward %
uint256 public borrowerPCT;

/// @dev Supply reward %
uint256 public supplierPCT;

/// @notice Emitted when pendingAdmin is changed
event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);

Expand Down Expand Up @@ -40,12 +68,17 @@ contract RewardsDistributorDelegate is RewardsDistributorDelegateStorageV1, Expo
/// @notice The initial COMP index for a market
uint224 public constant compInitialIndex = 1e36;

/// @dev Intitializer to set admin to caller and set reward token
function initialize(address _rewardToken) external {
/// @dev Intitializer to set admin to caller and set reward token and start time of rewards
function initialize(address _rewardToken, uint256 _startTime, uint256 _borrowerPCT) external {
require(msg.sender == admin, "Only admin can initialize.");
require(rewardToken == address(0), "Already initialized.");
require(_rewardToken != address(0), "Cannot initialize reward token to the zero address.");
require(_startTime != 0, "Cannot initialize start time to the zero address.");

rewardToken = _rewardToken;
startTime = _startTime;
borrowerPCT = _borrowerPCT;
supplierPCT = TOTAL_PCT.sub(_borrowerPCT);
}

/*** Set Admin ***/
Expand Down Expand Up @@ -106,7 +139,7 @@ contract RewardsDistributorDelegate is RewardsDistributorDelegateStorageV1, Expo
// Make sure distributor is added
bool distributorAdded = false;
address[] memory distributors = comptroller.getRewardsDistributors();
for (uint256 i = 0; i < distributors.length; i++) if (distributors[i] == address(this)) distributorAdded = true;
for (uint256 i = 0; i < distributors.length; i++) if (distributors[i] == address(this)) distributorAdded = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gas optimization for loop:

uint256 distributorsLen = distributors.length;
for (uint256 i = 0; i < distributorsLen; ++i) 

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

is distributors.length really calculated multiple times? this code was not changed its from original comp

require(distributorAdded == true, "distributor not added");
}

Expand Down Expand Up @@ -435,6 +468,38 @@ contract RewardsDistributorDelegate is RewardsDistributorDelegateStorageV1, Expo
setCompBorrowSpeedInternal(cToken, compSpeed);
}

/**
* @notice Set borrower PCT
*/
function _setBorrowerPCT(uint256 _borrowerPCT) public {
require(msg.sender == admin, "only admin can set borrower percent");
borrowerPCT = _borrowerPCT;
supplierPCT = TOTAL_PCT.sub(_borrowerPCT);
}

/**
* @notice Set new borrow / supply speed
* @param cToken The market whose COMP speed to update
*/

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

_updateSpeedWithNewEpoch needs to be called from cron job weekly

function updateSpeedWithNewEpoch(CToken cToken) external {
require(block.timestamp.sub(startTime) >= WEEK, "Must be at least week since latest epoch");

@chudnov chudnov Apr 5, 2022

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

potential issues:

1: if _updateSpeedWithNewEpoch not called from cron job w fast enough cadence it could be spreading rewards for longer duration than the speed duration was set for (longer than a week). But should not be a problem as long as we continue minting each week so even if we call late they can claim from balance of subsequent (essentially kick can down road indefinitely)

2: if AVG_BLOCKS_PER_WEEK is off, we could be giving out more rewards than speed duration set for (ex: we assume AVG is x but we end up having 1.0025x blocks so we spread out same rewards for more blocks. But should average out and again should not be a problem as long as we continue minting each week so even if we call late they can claim from balance of subsequent (essentially kick can down road indefinitely).

uint256 totalToDistribute = totalMint.sub(lastEpochTotalMint).div(AVG_BLOCKS_PER_WEEK);
uint256 toDistributeToBorrower = toDistribute.mul(borrowerPCT).div(TOTAL_PCT);
lastEpochTotalMint = totalMint;
startTime = startTime.add(WEEK);
setCompBorrowSpeedInternal(cToken, toDistributeToBorrower);
setCompSupplySpeedInternal(cToken, toDistribute.sub(toDistributeToBorrower));
}

/**
* @notice Burn
* @param Takes in RBN tokens
*/
function burn(uint256 amount) external {
IERC20(rewardToken).transferFrom(msg.sender, address(this), amount);
totalMint = totalMint.add(amount);
}

/**
* @notice Set COMP borrow and supply speeds for the specified markets.
* @param cTokens The markets whose COMP speed to update.
Expand Down
Loading