Skip to content

Commit

Permalink
fix: rewards (#4)
Browse files Browse the repository at this point in the history
* fix: rewards

* feat: add univ3
  • Loading branch information
spalen0 authored Dec 6, 2022
1 parent feae7a7 commit adc4d67
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 231 deletions.
130 changes: 88 additions & 42 deletions contracts/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/access/Ownable.sol";

import {IERC20, BaseStrategy} from "BaseStrategy.sol";
import "./interfaces/IVault.sol";
import "./interfaces/IUniswapV2Router01.sol";
import "./interfaces/ISwapRouter.sol";
import "./interfaces/ITradeFactory.sol";
import "./interfaces/comp/CErc20I.sol";
import "./interfaces/comp/InterestRateModel.sol";
Expand All @@ -17,10 +17,15 @@ import "./interfaces/comp/UniswapAnchoredViewI.sol";
contract Strategy is BaseStrategy, Ownable {
using SafeERC20 for IERC20;

//Uniswap v3 router
ISwapRouter internal constant UNISWAP_ROUTER =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
//Fees for the V3 pools if the supply is incentivized
uint24 public compToEthFee;
uint24 public ethToWantFee;

// eth blocks are mined every 12s -> 3600 * 24 * 365 / 12 = 2_628_000
uint256 private constant BLOCKS_PER_YEAR = 2_628_000;
address public constant UNISWAP_ROUTER =
address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
address public constant COMP =
address(0xc00e94Cb662C3520282E6f5717214004A7f26888);
address public constant WETH =
Expand Down Expand Up @@ -68,11 +73,15 @@ contract Strategy is BaseStrategy, Ownable {
_amountFreed = _amount;
} else {
// NOTE: we need the balance updated
// balanceOfUnderlying accrues interest in a transaction
uint256 balanceUnderlying = cToken.balanceOfUnderlying(
address(this)
);
// We need to take from Compound enough to reach _amount
// We run with 'unchecked' as we are safe from underflow
unchecked {
_withdrawFromCompound(
Math.min(_amount - idleAmount, balanceOfCToken())
Math.min(_amount - idleAmount, balanceUnderlying)
);
}
_amountFreed = balanceOfAsset();
Expand Down Expand Up @@ -152,45 +161,67 @@ contract Strategy is BaseStrategy, Ownable {
function getRewardAprForSupplyBase(
int256 newAmount
) public view returns (uint256) {
// COMP issued per block to suppliers * (1 * 10 ^ 18)
uint256 compSpeedPerBlock = COMPTROLLER.compSupplySpeeds(
address(cToken)
);
if (compSpeedPerBlock == 0) {
return 0;
}
// Approximate COMP issued per year to suppliers * (1 * 10 ^ 18)
uint256 compSpeedPerYear = compSpeedPerBlock * BLOCKS_PER_YEAR;

// The price of the asset in USD as an unsigned integer scaled up by 10 ^ 6
uint256 rewardTokenPriceInUsd = PRICE_FEED.price("COMP");

uint256 assetDecimals = IVault(vault).decimals();

// https://docs.compound.finance/v2/prices/#underlying-price
// The price of the asset in USD as an unsigned integer scaled up by 10 ^ (36 - underlying asset decimals)
uint256 wantPriceInUsd = PRICE_FEED.getUnderlyingPrice(address(cToken));
// upscale to price COMP percision 10 ^ 6
uint256 wantPriceInUsd = PRICE_FEED.getUnderlyingPrice(
address(cToken)
) / 10 ** (30 - assetDecimals);

uint256 cTokenTotalSupplyInWant = (cToken.totalSupply() *
cToken.exchangeRateStored()) / 1e18;
uint256 wantTotalSupply = uint256(
int256(cToken.totalSupply()) + newAmount
int256(cTokenTotalSupplyInWant) + newAmount
);

// COMP issued per block to suppliers OR borrowers * (1 * 10 ^ 18)
uint256 compSpeed = COMPTROLLER.compSpeeds(address(cToken));
// Approximate COMP issued per year to suppliers OR borrowers * (1 * 10 ^ 18)
uint256 compSpeedPerYear = compSpeed * BLOCKS_PER_YEAR;
// result 1e18 = 1e6 * 1e12 * 1e18 / 1e18
uint256 supplyBaseRewardApr = (rewardTokenPriceInUsd *
1e12 *
compSpeedPerYear) / (wantTotalSupply * wantPriceInUsd);

uint256 decimals = IVault(vault).decimals();
if (decimals < 18) {
// scale value to 1e18, see wantPriceInUsd scaling above
supplyBaseRewardApr = supplyBaseRewardApr / (10 ** (18 - decimals));
}
return supplyBaseRewardApr;
return
(compSpeedPerYear * rewardTokenPriceInUsd * 10 ** assetDecimals) /
(wantTotalSupply * wantPriceInUsd);
}

/**
* @notice Get pending COMP rewards for supplying want token
* @dev Pending rewards are update in comptroller afer every ctoken mint or redeem
* @return Amount of pending COMP tokens
*/
function getRewardsPending() public view returns (uint256) {
return COMPTROLLER.compAccrued(address(this));
// https://github.com/compound-finance/compound-protocol/blob/master/contracts/Comptroller.sol#L1230
ComptrollerI.CompMarketState memory supplyState = COMPTROLLER
.compSupplyState(address(cToken));
uint256 supplyIndex = supplyState.index;
uint256 supplierIndex = COMPTROLLER.compSupplierIndex(
address(cToken),
address(this)
);

// Calculate change in the cumulative sum of the COMP per cToken accrued
uint256 deltaIndex = supplyIndex - supplierIndex;

// Calculate COMP accrued: cTokenAmount * accruedPerCToken / doubleScale
return (cToken.balanceOf(address(this)) * deltaIndex) / 1e36;
}

function harvest() external onlyOwner {
_claimRewards();
if (getRewardsPending() > minCompToClaim) {
_claimRewards();
}

if (tradeFactory == address(0)) {
if (tradeFactory == address(0) && compToEthFee != 0) {
_disposeOfComp();
}

Expand All @@ -208,34 +239,49 @@ contract Strategy is BaseStrategy, Ownable {
* Claims the reward tokens due to this contract address
*/
function _claimRewards() internal {
if (COMPTROLLER.compAccrued(address(this)) > minCompToClaim) {
CTokenI[] memory cTokens = new CTokenI[](1);
cTokens[0] = cToken;
address[] memory holders = new address[](1);
holders[0] = address(this);
COMPTROLLER.claimComp(holders, cTokens, false, true);
}
CTokenI[] memory cTokens = new CTokenI[](1);
cTokens[0] = cToken;
address[] memory holders = new address[](1);
holders[0] = address(this);
COMPTROLLER.claimComp(holders, cTokens, false, true);
}

function _disposeOfComp() internal {
uint256 compBalance = IERC20(COMP).balanceOf(address(this));

if (compBalance > minCompToSell) {
address[] memory path = new address[](3);
path[0] = COMP;
path[1] = WETH;
path[2] = IVault(vault).asset();

IUniswapV2Router01(UNISWAP_ROUTER).swapExactTokensForTokens(
compBalance,
uint256(0),
path,
address(this),
block.timestamp
bytes memory path = abi.encodePacked(
COMP, // comp-ETH
compToEthFee,
WETH, // ETH-want
ethToWantFee,
IVault(vault).asset()
);

// Proceeds from Comp are not subject to minExpectedSwapPercentage
// so they could get sandwiched if we end up in an uncle block
UNISWAP_ROUTER.exactInput(
ISwapRouter.ExactInputParams(
path,
address(this),
block.timestamp,
compBalance,
0
)
);
}
}

//These will default to 0.
//Will need to be manually set if want is incentized before any harvests
function setUniFees(
uint24 _compToEth,
uint24 _ethToWant
) external onlyOwner {
compToEthFee = _compToEth;
ethToWantFee = _ethToWant;
}

/**
* @notice Set values for handling COMP reward token
* @param _minCompToSell Minimum value that will be sold
Expand Down
95 changes: 95 additions & 0 deletions contracts/interfaces/ISwapRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.14;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}

/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(
ExactInputSingleParams calldata params
) external payable returns (uint256 amountOut);

struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}

/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(
ExactInputParams calldata params
) external payable returns (uint256 amountOut);

struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}

/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(
ExactOutputSingleParams calldata params
) external payable returns (uint256 amountIn);

struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}

/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(
ExactOutputParams calldata params
) external payable returns (uint256 amountIn);

// Taken from https://soliditydeveloper.com/uniswap3
// Manually added to the interface
function refundETH() external payable;
}
Loading

0 comments on commit adc4d67

Please sign in to comment.