Skip to content

Commit

Permalink
feat: adapt to compound
Browse files Browse the repository at this point in the history
  • Loading branch information
jmonteer committed Nov 6, 2022
1 parent fbee6d3 commit 2a974c9
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 122 deletions.
1 change: 0 additions & 1 deletion contracts/BaseStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity 0.8.14;
pragma experimental ABIEncoderV2;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

Expand Down
106 changes: 30 additions & 76 deletions contracts/Strategy.sol
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity 0.8.14;

import "@openzeppelin/contracts/utils/math/Math.sol";

import {IERC20, BaseStrategy} from "BaseStrategy.sol";

import "./interfaces/aave/ILendingPool.sol";
import "./interfaces/aave/ILendingPoolAddressesProvider.sol";
import "./interfaces/aave/IProtocolDataProvider.sol";
import "./interfaces/aave/IReserveInterestRateStrategy.sol";
import "./libraries//aave/DataTypes.sol";

import "interfaces/IVault.sol";
import {Comet, CometStructs, CometRewards} from "interfaces/CompoundV3.sol";
contract Strategy is BaseStrategy {
IProtocolDataProvider public constant PROTOCOL_DATA_PROVIDER =
IProtocolDataProvider(0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d);

address public immutable aToken;

// TODO: add rewards
Comet public immutable cToken;
uint256 private SECONDS_PER_YEAR = 365 days;
constructor(
address _vault,
string memory _name
string memory _name,
Comet _cToken
) BaseStrategy(_vault, _name) {
(address _aToken, , ) = PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(
asset
);
aToken = _aToken;
cToken = _cToken;
require(cToken.baseToken() == IVault(vault).asset());
}

function _maxWithdraw(
address owner
) internal view override returns (uint256) {
return Math.min(IERC20(asset).balanceOf(aToken), _totalAssets());
// TODO: may not be accurate due to unaccrued balance in cToken
return Math.min(IERC20(asset).balanceOf(address(cToken)), _totalAssets());
}

function _freeFunds(
Expand All @@ -42,17 +34,17 @@ contract Strategy is BaseStrategy {
// we have enough idle assets for the vault to take
_amountFreed = _amount;
} else {
// NOTE: we need the balance updated
cToken.accrueAccount(address(this));
// We need to take from Aave enough to reach _amount
// Balance of
// We run with 'unchecked' as we are safe from underflow
unchecked {
_withdrawFromAave(
Math.min(_amount - _idleAmount, balanceOfAToken())
_withdrawFromComet(
Math.min(_amount - _idleAmount, balanceOfCToken())
);
}
_amountFreed = balanceOfAsset();


}
}

Expand All @@ -65,25 +57,22 @@ contract Strategy is BaseStrategy {
}

function _totalAssets() internal view override returns (uint256) {
return balanceOfAsset() + balanceOfAToken();
return balanceOfAsset() + balanceOfCToken();
}

function _invest() internal override {
uint256 _availableToInvest = balanceOfAsset();
require(_availableToInvest > 0, "no funds to invest");
_depositToAave(_availableToInvest);
_depositToComet(_availableToInvest);
}

function _depositToAave(uint256 amount) internal {
ILendingPool lp = _lendingPool();
_checkAllowance(address(lp), asset, amount);
lp.deposit(asset, amount, address(this), 0);
function _withdrawFromComet(uint256 _amount) internal {
cToken.withdraw(address(asset), _amount);
}

function _withdrawFromAave(uint256 amount) internal {
ILendingPool lp = _lendingPool();
_checkAllowance(address(lp), aToken, amount);
lp.withdraw(asset, amount, address(this));
function _depositToComet(uint256 _amount) internal {
Comet _cToken = cToken;
_checkAllowance(address(_cToken), asset, _amount);
_cToken.supply(address(asset), _amount);
}

function _checkAllowance(
Expand All @@ -97,55 +86,20 @@ contract Strategy is BaseStrategy {
}
}

function _lendingPool() internal view returns (ILendingPool) {
return
ILendingPool(
PROTOCOL_DATA_PROVIDER.ADDRESSES_PROVIDER().getLendingPool()
);
}

function balanceOfAToken() internal view returns (uint256) {
return IERC20(aToken).balanceOf(address(this));
function balanceOfCToken() internal view returns (uint256) {
return IERC20(cToken).balanceOf(address(this));
}

function balanceOfAsset() internal view returns (uint256) {
return IERC20(asset).balanceOf(address(this));
}

function aprAfterDebtChange(int256 delta) external view returns (uint256) {
// i need to calculate new supplyRate after Deposit (when deposit has not been done yet)
DataTypes.ReserveData memory reserveData = _lendingPool()
.getReserveData(address(asset));

(
uint256 availableLiquidity,
uint256 totalStableDebt,
uint256 totalVariableDebt,
,
,
,
uint256 averageStableBorrowRate,
,
,

) = PROTOCOL_DATA_PROVIDER.getReserveData(address(asset));

int256 newLiquidity = int256(availableLiquidity) + delta;

(, , , , uint256 reserveFactor, , , , , ) = PROTOCOL_DATA_PROVIDER
.getReserveConfigurationData(address(asset));

(uint256 newLiquidityRate, , ) = IReserveInterestRateStrategy(
reserveData.interestRateStrategyAddress
).calculateInterestRates(
address(asset),
uint256(newLiquidity),
totalStableDebt,
totalVariableDebt,
averageStableBorrowRate,
reserveFactor
);
uint256 borrows = cToken.totalBorrow();
uint256 supply = cToken.totalSupply();

return newLiquidityRate / 1e9; // ray to wad
uint256 newUtilization = borrows * 1e18 / uint256(int256(supply) + delta);
uint256 newSupplyRate = cToken.getSupplyRate(newUtilization) * SECONDS_PER_YEAR;
return newSupplyRate;
}
}
23 changes: 7 additions & 16 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import pytest
from ape import Contract, accounts
from ape import Contract, accounts, project
from utils.constants import MAX_INT, WEEK, ROLES

# this should be the address of the ERC-20 used by the strategy/vault
ASSET_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" # USDC
AASSET_ADDRESS = "0xBcca60bB61934080951369a648Fb03DF4F96263C" # AUSDC
CASSET_ADDRESS = "0xc3d688B66703497DAA19211EEdff47f25384cdc3" # cUSDCv3
ASSET_WHALE_ADDRESS = "0x0A59649758aa4d66E25f08Dd01271e891fe52199" # USDC WHALE


Expand Down Expand Up @@ -36,8 +36,9 @@ def amount(asset):


@pytest.fixture(scope="session")
def atoken():
return Contract(AASSET_ADDRESS)
def ctoken():
# NOTE: adding default contract type because it's not verified
return Contract(CASSET_ADDRESS, project.Comet.contract_type)


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -75,7 +76,7 @@ def vault(gov, asset, create_vault):
@pytest.fixture
def create_strategy(project, strategist):
def create_strategy(vault):
strategy = strategist.deploy(project.Strategy, vault.address, "strategy_name")
strategy = strategist.deploy(project.Strategy, vault.address, "strategy_name", CASSET_ADDRESS)
return strategy

yield create_strategy
Expand Down Expand Up @@ -121,15 +122,5 @@ def provide_strategy_with_debt(account, strategy, vault, target_debt: int):
@pytest.fixture
def user_interaction(strategy, vault, deposit_into_vault):
def user_interaction():
# Due to the fact that Aave doesn't update internal state till new txs are
# created, we force it by creating a withdraw
awhale = "0x13873fa4B7771F3492825B00D1c37301fF41C348"
lp = Contract("0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9")
lp.withdraw(
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
int(10**6),
awhale,
sender=accounts[awhale],
)

return
yield user_interaction
Loading

0 comments on commit 2a974c9

Please sign in to comment.