Skip to content

Commit

Permalink
feat: resolve base asset
Browse files Browse the repository at this point in the history
  • Loading branch information
xenide committed Jun 9, 2024
1 parent afa4cc3 commit ebe983e
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 5 deletions.
31 changes: 26 additions & 5 deletions src/ReservoirPriceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg.
event DesignatePair(address token0, address token1, ReservoirPair pair);
event FallbackOracleSet(address fallbackOracle);
event PriceDeviationThreshold(uint256 newThreshold);
event ResolvedVaultSet(address vault, address asset);
event RewardGasAmount(uint256 newAmount);
event Route(address token0, address token1, address[] route);
event Price(address token0, address token1, uint256 price);
Expand Down Expand Up @@ -69,6 +70,9 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg.
/// @notice Designated pairs to serve as price feed for a certain token0 and token1
mapping(address token0 => mapping(address token1 => ReservoirPair pair)) public pairs;

/// @notice ERC4626 vaults resolved using internal pricing (`convertToAssets`).
mapping(address vault => address asset) public resolvedVaults;

///////////////////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR, FALLBACKS //
///////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -382,7 +386,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg.
}
}

function _getQuotes(uint256 aAmount, address aBase, address aQuote, bool isGetQuotes)
function _getQuotes(uint256 aAmount, address aBase, address aQuote, bool aIsGetQuotes)
internal
view
returns (uint256 rBidOut, uint256 rAskOut)
Expand All @@ -396,11 +400,14 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg.

// route does not exist on our oracle, attempt querying the fallback
if (lRoute.length == 0) {
if (fallbackOracle == address(0)) revert OracleErrors.NoPath();
address lBaseAsset = resolvedVaults[aBase];

if (lBaseAsset != address(0)) {
uint256 lResolvedAmountIn = IERC4626(aBase).convertToAssets(aAmount);
return _getQuotes(lResolvedAmountIn, lBaseAsset, aQuote, aIsGetQuotes);
}

// We do not catch errors here so the fallback oracle will revert if it doesn't support the query.
if (isGetQuotes) (rBidOut, rAskOut) = IPriceOracle(fallbackOracle).getQuotes(aAmount, aBase, aQuote);
else rBidOut = rAskOut = IPriceOracle(fallbackOracle).getQuote(aAmount, aBase, aQuote);
return _useFallbackOracle(aAmount, aBase, aQuote, aIsGetQuotes);
} else if (lRoute.length == 2) {
if (lPrice == 0) revert OracleErrors.PriceZero();
rBidOut = rAskOut = _calcAmtOut(aAmount, lPrice, lDecimalDiff, lRoute[0] != aBase);
Expand Down Expand Up @@ -454,6 +461,14 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg.
}
}

function _useFallbackOracle(uint256 aAmount, address aBase, address aQuote, bool aIsGetQuotes) internal view returns (uint256 rBidOut, uint256 rAskOut) {
if (fallbackOracle == address(0)) revert OracleErrors.NoPath();

// We do not catch errors here so the fallback oracle will revert if it doesn't support the query.
if (aIsGetQuotes) (rBidOut, rAskOut) = IPriceOracle(fallbackOracle).getQuotes(aAmount, aBase, aQuote);
else rBidOut = rAskOut = IPriceOracle(fallbackOracle).getQuote(aAmount, aBase, aQuote);
}

///////////////////////////////////////////////////////////////////////////////////////////////
// ADMIN FUNCTIONS //
///////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -506,6 +521,12 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg.
emit SetPriceType(aType);
}

function setResolvedVault(address aVault, bool aSet) external onlyOwner {
address lAsset = aSet ? IERC4626(aVault).asset() : address(0);
resolvedVaults[aVault] = lAsset;
emit ResolvedVaultSet(aVault, lAsset);
}

/// @notice Sets the price route between aToken0 and aToken1, and also intermediate routes if previously undefined
/// @param aToken0 Address of the lower token
/// @param aToken1 Address of the higher token
Expand Down
32 changes: 32 additions & 0 deletions test/mock/StubERC4626.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

contract StubERC4626 {
address public asset;
uint256 private rate;
string revertMsg = "oops";

Check warning on line 7 in test/mock/StubERC4626.sol

View workflow job for this annotation

GitHub Actions / lint

Explicitly mark visibility of state
bool doRevert;

Check warning on line 8 in test/mock/StubERC4626.sol

View workflow job for this annotation

GitHub Actions / lint

Explicitly mark visibility of state

constructor(address _asset, uint256 _rate) {
asset = _asset;
rate = _rate;
}

function setRevert(bool _doRevert) external {
doRevert = _doRevert;
}

function setRate(uint256 _rate) external {
rate = _rate;
}

function convertToAssets(uint256 shares) external view returns (uint256) {
if (doRevert) revert(revertMsg);
return shares * rate / 1e18;
}

function convertToShares(uint256 assets) external view returns (uint256) {
if (doRevert) revert(revertMsg);
return assets * 1e18 / rate;
}
}
18 changes: 18 additions & 0 deletions test/unit/ReservoirPriceOracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Bytes32Lib } from "amm-core/libraries/Bytes32.sol";
import { EnumerableSetLib } from "lib/solady/src/utils/EnumerableSetLib.sol";
import { Constants } from "src/libraries/Constants.sol";
import { MockFallbackOracle } from "test/mock/MockFallbackOracle.sol";
import { StubERC4626 } from "test/mock/StubERC4626.sol";

contract ReservoirPriceOracleTest is BaseTest {
using Utils for *;
Expand Down Expand Up @@ -486,6 +487,23 @@ contract ReservoirPriceOracleTest is BaseTest {
assertGt(lAskOut, 0);
}

function testGetQuote_BaseIsVault(uint256 aRate) external {
// assume
uint256 lRate = bound(aRate, 1, 1e36);

// arrange
uint256 lAmtIn = 5e18;
StubERC4626 lVault = new StubERC4626(address(_tokenA), lRate);
_oracle.setResolvedVault(address(lVault), true);
_writePriceCache(address(_tokenA), address(_tokenB), 1e18);

// act
uint256 lAmtOut = _oracle.getQuote(lAmtIn, address(lVault), address(_tokenB));

// assert
assertEq(lAmtOut / 1e12, lAmtIn * lRate / 1e18);
}

function testUpdatePriceDeviationThreshold(uint256 aNewThreshold) external {
// assume
uint64 lNewThreshold = uint64(bound(aNewThreshold, 0, 0.1e18));
Expand Down

0 comments on commit ebe983e

Please sign in to comment.