Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UniswapV3 strategy #26

Open
wants to merge 60 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2e04e6c
Add UniswapV3 strategy
parv3213 Oct 20, 2023
1bdbd9b
Fix `checkInterestEarned`
parv3213 Oct 20, 2023
a6a5631
Split and Send Reward only when amount is greater than 0
parv3213 Oct 23, 2023
69c5ec2
Collect tokens after decreasing liquidity
parv3213 Oct 23, 2023
3613ce9
Add `_validateTickRange` check
parv3213 Oct 23, 2023
4fa7923
Add starter tests
parv3213 Oct 23, 2023
c459029
Fix custom errors
parv3213 Oct 23, 2023
ea3a198
Improve tests
parv3213 Oct 23, 2023
e7d1470
Merge remote-tracking branch 'origin/dev' into wip/uniswap-strategy
parv3213 Oct 25, 2023
a2142ec
Merge remote-tracking branch 'origin/dev' into wip/uniswap-strategy
parv3213 Oct 25, 2023
365e4d1
Fix removePToken to delete both pair tokens and `uniswapPoolData`
parv3213 Oct 25, 2023
e8d8736
Incremental tests
parv3213 Oct 25, 2023
f8f39cb
Update contracts/strategies/uniswap/UniswapStrategy.sol
parv3213 Oct 25, 2023
d4fc0b7
Optimize `_withdraw`
parv3213 Oct 25, 2023
ebf23e9
Use `safeIncreaseAllowance` in place of `safeApprove`
parv3213 Oct 26, 2023
8e1b67a
Incremental tests
parv3213 Oct 26, 2023
e134538
Modifications-
parv3213 Oct 27, 2023
6e2bdb4
Update tests as per modifications
parv3213 Oct 27, 2023
d41687d
Use liquidity to calculate allocated amount
parv3213 Oct 30, 2023
5999a57
Merge remote-tracking branch 'origin/dev' into wip/uniswap-strategy
parv3213 Oct 30, 2023
550cdfd
Use `getAmountsForLiquidity`
parv3213 Oct 31, 2023
7df72d4
Use external UniswapUtils
parv3213 Oct 31, 2023
948256a
Fix edge cases
parv3213 Oct 31, 2023
9b48efd
Add incremental tests
parv3213 Oct 31, 2023
bc3cc3c
Add link to contract addresses
parv3213 Nov 1, 2023
a546f57
Update contracts/strategies/uniswap/UniswapStrategy.sol
parv3213 Nov 1, 2023
0573378
Add incomplete tests for `CollectInterest`
parv3213 Nov 1, 2023
f8ea968
Update contracts/strategies/uniswap/UniswapStrategy.sol
parv3213 Nov 1, 2023
459bce5
Remove virtual keyword
parv3213 Nov 1, 2023
64b089b
Add incremental tests
parv3213 Nov 1, 2023
635cc7b
Push failing `collectInterest` test
parv3213 Nov 1, 2023
10d1588
Fix `collectInterest` tests
parv3213 Nov 1, 2023
7e9774c
Optimizations
parv3213 Nov 1, 2023
604b830
Optimization
parv3213 Nov 1, 2023
1d5f60c
Fix stack-too-deep on `forge-coverage-minimum`
parv3213 Nov 1, 2023
03a09d0
Update Solhint config
parv3213 Nov 2, 2023
268ba17
Add custom error and resolve all solhint warnings
parv3213 Nov 2, 2023
02d7bdc
Rename all test contract to have `.t.sol`
parv3213 Nov 2, 2023
acbc67f
Add solhint check in CI
parv3213 Nov 3, 2023
d629dd0
Added slippage while initialization and allocation
arcantheon Nov 6, 2023
a547ecb
Updated test cases
arcantheon Nov 6, 2023
a902940
Refactor UniswapStrategy initialization and
parv3213 Nov 6, 2023
e353a38
Rename redeem function parameters.
parv3213 Nov 7, 2023
8c63954
Removed unused assertion
arcantheon Nov 7, 2023
9b470d9
Merge branch 'wip/uniswap-strategy' of github.com:Sperax/USDs-v2 into…
arcantheon Nov 7, 2023
273156a
Remove onlyOwner modifier from allocate function
parv3213 Nov 7, 2023
4d12b13
Added redeem test cases
arcantheon Nov 7, 2023
f7c56c6
Refactor UniswapStrategy to use
parv3213 Nov 7, 2023
48ad03c
Fix Natspec
parv3213 Nov 7, 2023
9c261cd
Added redeem assertions
arcantheon Nov 7, 2023
099e76c
Added uniswap strategy test cases
arcantheon Nov 7, 2023
72e355d
removed unused function
arcantheon Nov 7, 2023
4c5286b
Added slippage check for redeem
arcantheon Nov 7, 2023
359bfe6
Fixed Uniswap strategy test cases
arcantheon Nov 9, 2023
c3a97fc
Convert public function to external that were not used inside the con…
parv3213 Nov 7, 2023
a0c0da1
Fix stack-too-deep error by adding NonfungiblePositionManagerUtils
parv3213 Nov 17, 2023
dc1122c
Merge remote-tracking branch 'origin/dev' into wip/uniswap-strategy
parv3213 Dec 18, 2023
0e72831
Fix solidity version for new files
parv3213 Dec 18, 2023
bb9c70a
Merge remote-tracking branch 'origin/dev' into wip/uniswap-strategy
parv3213 Dec 18, 2023
c1bd2ce
Remove `forge-coverage-minimum` script
parv3213 Dec 18, 2023
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
3 changes: 3 additions & 0 deletions .github/workflows/PR-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
with:
version: nightly

- name: Check Solhint
run: npm run lint-contract

- name: Check formatting
run: npm run prettier-check

Expand Down
7 changes: 4 additions & 3 deletions .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
"extends": "solhint:recommended",

"rules": {
"compiler-version": "off",
"compiler-version": ["error", "0.8.16"],
"ordering": "error",
"avoid-throw": "off",
"avoid-suicide": "error",
"avoid-sha3": "warn",
"not-rely-on-time": "off",
"no-console": "warn",
"no-console": "error",
"func-named-parameters": ["warn", 4],
"func-visibility": ["warn", { "ignoreConstructors": true }]
"func-visibility": ["error", { "ignoreConstructors": true }],
"one-contract-per-file": "off"
}
}
42 changes: 26 additions & 16 deletions contracts/oracle/BaseUniOracle.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
pragma solidity 0.8.16;

import {ERC20} from "@openzeppelin/contracts_v3.4.2/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts_v3.4.2/access/Ownable.sol";
import {SafeMath} from "@openzeppelin/contracts_v3.4.2/math/SafeMath.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";
import {IUniswapUtils} from "../strategies/uniswap/interfaces/IUniswapUtils.sol";

interface IMasterPriceOracle {
/// @notice Validates if price feed exists for a `_token`
Expand All @@ -25,9 +23,9 @@ interface IMasterPriceOracle {
/// @author Sperax Foundation
/// @notice Has all the base functionalities, variables etc to be implemented by child contracts
abstract contract BaseUniOracle is Ownable {
using SafeMath for uint256;

address public constant UNISWAP_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
// TODO RECHECK!!!
address public constant UNISWAP_UTILS = 0xd2Aa19D3B7f8cdb1ea5B782c5647542055af415e;

address public masterOracle; // Address of the master price oracle
address public pool; // Address of the uniswap pool for the token and quoteToken
Expand All @@ -36,9 +34,13 @@ abstract contract BaseUniOracle is Ownable {
uint128 public quoteTokenPrecision; // QuoteToken price precision

event MasterOracleUpdated(address newOracle);

event UniMAPriceDataChanged(address quoteToken, uint24 feeTier, uint32 maPeriod);

// Custom Errors
error QuoteTokenFeedMissing();
error FeedUnavailable();
error InvalidAddress();

/// @notice A function to get price
/// @return (uint256, uint256) Returns price and price precision
function getPrice() external view virtual returns (uint256, uint256);
Expand All @@ -48,7 +50,9 @@ abstract contract BaseUniOracle is Ownable {
function updateMasterOracle(address _newOracle) public onlyOwner {
_isNonZeroAddr(_newOracle);
masterOracle = _newOracle;
require(IMasterPriceOracle(_newOracle).priceFeedExists(quoteToken), "Quote token feed missing");
if (!IMasterPriceOracle(_newOracle).priceFeedExists(quoteToken)) {
revert QuoteTokenFeedMissing();
}
emit MasterOracleUpdated(_newOracle);
}

Expand All @@ -62,10 +66,14 @@ abstract contract BaseUniOracle is Ownable {
onlyOwner
{
address uniOraclePool = IUniswapV3Factory(UNISWAP_FACTORY).getPool(_token, _quoteToken, _feeTier);
require(uniOraclePool != address(0), "Feed unavailable");
if (uniOraclePool == address(0)) {
revert FeedUnavailable();
}

// Validate if the oracle has a price feed for the _quoteToken
require(IMasterPriceOracle(masterOracle).priceFeedExists(_quoteToken), "Quote token feed missing");
if (!IMasterPriceOracle(masterOracle).priceFeedExists(_quoteToken)) {
revert QuoteTokenFeedMissing();
}

pool = uniOraclePool;
quoteToken = _quoteToken;
Expand All @@ -88,16 +96,18 @@ abstract contract BaseUniOracle is Ownable {
/// @dev tokenBPerTokenA has the same precision as tokenB
function _getUniMAPrice(address _tokenA, uint128 _tokenAPrecision) internal view returns (uint256) {
// get MA tick
uint32 oldestObservationSecondsAgo = OracleLibrary.getOldestObservationSecondsAgo(pool);
uint32 oldestObservationSecondsAgo = IUniswapUtils(UNISWAP_UTILS).getOldestObservationSecondsAgo(pool);
uint32 period = maPeriod < oldestObservationSecondsAgo ? maPeriod : oldestObservationSecondsAgo;
(int24 timeWeightedAverageTick,) = OracleLibrary.consult(pool, period);
(int24 timeWeightedAverageTick,) = IUniswapUtils(UNISWAP_UTILS).consult(pool, period);
// get MA price from MA tick
uint256 tokenBPerTokenA =
OracleLibrary.getQuoteAtTick(timeWeightedAverageTick, _tokenAPrecision, _tokenA, quoteToken);
IUniswapUtils(UNISWAP_UTILS).getQuoteAtTick(timeWeightedAverageTick, _tokenAPrecision, _tokenA, quoteToken);
return tokenBPerTokenA;
}

function _isNonZeroAddr(address _addr) internal pure {
require(_addr != address(0), "Invalid Address");
if (_addr == address(0)) {
revert InvalidAddress();
}
}
}
36 changes: 23 additions & 13 deletions contracts/oracle/SPAOracle.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma solidity 0.8.16;
pragma experimental ABIEncoderV2;

import {BaseUniOracle} from "./BaseUniOracle.sol";
Expand All @@ -24,9 +24,12 @@ contract SPAOracle is BaseUniOracle {

event DIAParamsUpdated(uint256 weightDIA, uint128 diaMaxTimeThreshold);

constructor(address _masterOracle, address _quoteToken, uint24 _feeTier, uint32 _maPeriod, uint256 _weightDIA)
public
{
// Custom Errors
error PriceTooOld();
error InvalidWeight();
error InvalidTime();

constructor(address _masterOracle, address _quoteToken, uint24 _feeTier, uint32 _maPeriod, uint256 _weightDIA) {
_isNonZeroAddr(_masterOracle);
masterOracle = _masterOracle;
setUniMAPriceData(SPA, _quoteToken, _feeTier, _maPeriod);
Expand All @@ -40,15 +43,17 @@ contract SPAOracle is BaseUniOracle {
function getPrice() external view override returns (uint256, uint256) {
uint256 weightUNI = MAX_WEIGHT - weightDIA;
// calculate weighted UNI USDsPerSPA
uint256 weightedSPAUniPrice = weightUNI.mul(_getSPAUniPrice());
uint256 weightedSPAUniPrice = weightUNI * _getSPAUniPrice();

// calculate weighted DIA USDsPerSPA
(uint128 spaDiaPrice, uint128 lastUpdated) = IDiaOracle(DIA_ORACLE).getValue("SPA/USD");

require(block.timestamp - lastUpdated <= diaMaxTimeThreshold, "Price too old");
if (block.timestamp - lastUpdated > diaMaxTimeThreshold) {
revert PriceTooOld();
}

uint256 weightedSPADiaPrice = weightDIA.mul(spaDiaPrice);
uint256 spaPrice = weightedSPAUniPrice.add(weightedSPADiaPrice).div(MAX_WEIGHT);
uint256 weightedSPADiaPrice = weightDIA * spaDiaPrice;
uint256 spaPrice = (weightedSPAUniPrice + weightedSPADiaPrice) / MAX_WEIGHT;
return (spaPrice, SPA_PRICE_PRECISION);
}

Expand All @@ -60,8 +65,14 @@ contract SPAOracle is BaseUniOracle {
/// @param _weightDIA weight for DIA price feed
/// @param _maxTime max age of price feed from DIA
function updateDIAParams(uint256 _weightDIA, uint128 _maxTime) public onlyOwner {
require(_weightDIA <= MAX_WEIGHT, "Invalid weight");
require(_maxTime > 120, "Invalid time"); // 120 is the update frequency
if (_weightDIA > MAX_WEIGHT) {
revert InvalidWeight();
}
// 120 is the update frequency
if (_maxTime <= 120) {
revert InvalidTime();
}

weightDIA = _weightDIA;
diaMaxTimeThreshold = _maxTime;
emit DIAParamsUpdated(_weightDIA, _maxTime);
Expand All @@ -72,8 +83,7 @@ contract SPAOracle is BaseUniOracle {
function _getSPAUniPrice() private view returns (uint256) {
uint256 quoteTokenAmtPerSPA = _getUniMAPrice(SPA, SPA_PRECISION);
(uint256 quoteTokenPrice, uint256 quoteTokenPricePrecision) = _getQuoteTokenPrice();
return quoteTokenPrice.mul(quoteTokenAmtPerSPA).mul(SPA_PRICE_PRECISION).div(quoteTokenPrecision).div(
quoteTokenPricePrecision
);
return ((quoteTokenPrice * quoteTokenAmtPerSPA * SPA_PRICE_PRECISION) / quoteTokenPrecision)
/ quoteTokenPricePrecision;
}
}
9 changes: 4 additions & 5 deletions contracts/oracle/USDsOracle.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
pragma solidity 0.8.16;

import {BaseUniOracle} from "./BaseUniOracle.sol";

Expand All @@ -12,7 +11,7 @@ contract USDsOracle is BaseUniOracle {
uint128 private constant USDS_PRECISION = 1e18;
uint128 private constant USDS_PRICE_PRECISION = 1e8;

constructor(address _masterOracle, address _quoteToken, uint24 _feeTier, uint32 _maPeriod) public {
constructor(address _masterOracle, address _quoteToken, uint24 _feeTier, uint32 _maPeriod) {
_isNonZeroAddr(_masterOracle);
masterOracle = _masterOracle;
setUniMAPriceData(USDS, _quoteToken, _feeTier, _maPeriod);
Expand All @@ -23,8 +22,8 @@ contract USDsOracle is BaseUniOracle {
function getPrice() external view override returns (uint256, uint256) {
uint256 quoteTokenAmtPerUSDs = _getUniMAPrice(USDS, USDS_PRECISION);
(uint256 quoteTokenPrice, uint256 quoteTokenPricePrecision) = _getQuoteTokenPrice();
uint256 usdsPrice = quoteTokenPrice.mul(quoteTokenAmtPerUSDs).mul(USDS_PRICE_PRECISION).div(quoteTokenPrecision)
.div(quoteTokenPricePrecision);
uint256 usdsPrice = ((quoteTokenPrice * quoteTokenAmtPerUSDs * USDS_PRICE_PRECISION) / quoteTokenPrecision)
/ quoteTokenPricePrecision;
return (usdsPrice, USDS_PRICE_PRECISION);
}
}
5 changes: 4 additions & 1 deletion contracts/strategies/aave/AaveStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ contract AaveStrategy is InitializableAbstractStrategy {
IAaveLendingPool public aavePool;
mapping(address => uint256) public allocatedAmount; // Tracks the allocated amount of an asset.

error NoRewardIncentive();

/// @notice Initializer for setting up strategy internal state. This overrides the
/// InitializableAbstractStrategy initializer as AAVE needs several extra
/// addresses for the rewards program.
Expand Down Expand Up @@ -101,8 +103,8 @@ contract AaveStrategy is InitializableAbstractStrategy {

/// @inheritdoc InitializableAbstractStrategy
function collectReward() external pure override {
revert("No reward incentive for AAVE");
// No reward token for Aave
revert NoRewardIncentive();
}

/// @inheritdoc InitializableAbstractStrategy
Expand All @@ -125,6 +127,7 @@ contract AaveStrategy is InitializableAbstractStrategy {
}

/// @inheritdoc InitializableAbstractStrategy
// TODO can be made external. Same for others.
function checkAvailableBalance(address _asset) public view override returns (uint256) {
uint256 availableLiquidity = IERC20(_asset).balanceOf(_getPTokenFor(_asset));
uint256 allocatedValue = allocatedAmount[_asset];
Expand Down
Loading
Loading