-
Notifications
You must be signed in to change notification settings - Fork 31
Implement cross pricing adapter #28
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a86eb86
feat: multi adapter
totomanov d818ad6
Merge remote-tracking branch 'origin/master' into multi-adapter
totomanov eb62c28
feat: cross adapter
totomanov 0781a51
Merge remote-tracking branch 'origin/master' into multi-adapter
totomanov 13f241a
chore: rename oracleCrossQuote
totomanov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
| pragma solidity 0.8.23; | ||
|
|
||
| import {BaseAdapter} from "src/adapter/BaseAdapter.sol"; | ||
| import {IPriceOracle} from "src/interfaces/IPriceOracle.sol"; | ||
| import {ScaleUtils, Scale} from "src/lib/ScaleUtils.sol"; | ||
|
|
||
| /// @title CrossAdapter | ||
| /// @author Euler Labs (https://www.eulerlabs.com/) | ||
| /// @notice PriceOracle that chains two adapters. | ||
| /// @dev For example, CrossAdapter can price wstETH/USD by querying a wstETH/stETH oracle and a stETH/USD oracle. | ||
| contract CrossAdapter is BaseAdapter { | ||
| /// @notice The address of the base asset. | ||
| address public immutable base; | ||
| /// @notice The address of the cross/through asset. | ||
| address public immutable cross; | ||
| /// @notice The address of the quote asset. | ||
| address public immutable quote; | ||
| /// @notice The oracle that resolves base/cross and cross/base. | ||
| /// @dev The oracle MUST be bidirectional. | ||
| address public immutable oracleBaseCross; | ||
| /// @notice The oracle that resolves quote/cross and cross/quote. | ||
| /// @dev The oracle MUST be bidirectional. | ||
| address public immutable oracleQuoteCross; | ||
|
|
||
| /// @notice Deploy a CrossAdapter. | ||
| /// @param _base The address of the base asset. | ||
| /// @param _cross The address of the cross/through asset. | ||
| /// @param _quote The address of the quote asset. | ||
| /// @param _oracleBaseCross The oracle that resolves base/cross and cross/base. | ||
| /// @param _oracleQuoteCross The oracle that resolves quote/cross and cross/quote. | ||
| /// @dev Both cross oracles MUST be bidirectional. | ||
| /// @dev Does not support bid/ask pricing. | ||
| constructor(address _base, address _cross, address _quote, address _oracleBaseCross, address _oracleQuoteCross) { | ||
| base = _base; | ||
| cross = _cross; | ||
| quote = _quote; | ||
| oracleBaseCross = _oracleBaseCross; | ||
| oracleQuoteCross = _oracleQuoteCross; | ||
| } | ||
|
|
||
| /// @notice Get a quote by chaining the cross oracles. | ||
| /// For the forward direction it calculates base/cross * cross/quote. | ||
| /// For the inverse direction it calculates quote/cross * cross/base. | ||
| /// @param inAmount The amount of `base` to convert. | ||
| /// @param _base The token that is being priced. | ||
| /// @param _quote The token that is the unit of account. | ||
| /// @return The converted amount by chaining the cross oracles. | ||
| function _getQuote(uint256 inAmount, address _base, address _quote) internal view override returns (uint256) { | ||
| bool inverse = ScaleUtils.getDirectionOrRevert(_base, base, _quote, quote); | ||
|
|
||
| if (inverse) { | ||
| inAmount = IPriceOracle(oracleQuoteCross).getQuote(inAmount, quote, cross); | ||
| return IPriceOracle(oracleBaseCross).getQuote(inAmount, cross, base); | ||
| } else { | ||
| inAmount = IPriceOracle(oracleBaseCross).getQuote(inAmount, base, cross); | ||
| return IPriceOracle(oracleQuoteCross).getQuote(inAmount, cross, quote); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
| pragma solidity 0.8.23; | ||
|
|
||
| import {Test} from "forge-std/Test.sol"; | ||
| import {CrossAdapter} from "src/adapter/CrossAdapter.sol"; | ||
|
|
||
| contract StubPriceOracle { | ||
| mapping(address => mapping(address => uint256)) prices; | ||
|
|
||
| function setPrice(address base, address quote, uint256 price) external { | ||
| prices[base][quote] = price; | ||
| } | ||
|
|
||
| function getQuote(uint256 inAmount, address base, address quote) external view returns (uint256) { | ||
| return _calcQuote(inAmount, base, quote); | ||
| } | ||
|
|
||
| function getQuotes(uint256 inAmount, address base, address quote) external view returns (uint256, uint256) { | ||
| return (_calcQuote(inAmount, base, quote), _calcQuote(inAmount, base, quote)); | ||
| } | ||
|
|
||
| function _calcQuote(uint256 inAmount, address base, address quote) internal view returns (uint256) { | ||
| return inAmount * prices[base][quote] / 1e18; | ||
| } | ||
| } | ||
|
|
||
| contract CrossAdapterTest is Test { | ||
| address BASE = makeAddr("BASE"); | ||
| address CROSS = makeAddr("CROSS"); | ||
| address QUOTE = makeAddr("QUOTE"); | ||
| StubPriceOracle oracleBaseCross; | ||
| StubPriceOracle oracleQuoteCross; | ||
| CrossAdapter oracle; | ||
|
|
||
| function setUp() public { | ||
| oracleBaseCross = new StubPriceOracle(); | ||
| oracleQuoteCross = new StubPriceOracle(); | ||
| oracle = new CrossAdapter(BASE, CROSS, QUOTE, address(oracleBaseCross), address(oracleQuoteCross)); | ||
| } | ||
|
|
||
| function test_Constructor_Integrity() public view { | ||
| assertEq(oracle.base(), BASE); | ||
| assertEq(oracle.cross(), CROSS); | ||
| assertEq(oracle.quote(), QUOTE); | ||
| assertEq(oracle.oracleBaseCross(), address(oracleBaseCross)); | ||
| assertEq(oracle.oracleQuoteCross(), address(oracleQuoteCross)); | ||
| } | ||
|
|
||
|
|
||
|
|
||
| function test_GetQuote_Integrity(uint256 inAmount, uint256 priceBaseCross, uint256 priceCrossQuote) public { | ||
| inAmount = bound(inAmount, 0, type(uint128).max); | ||
| priceBaseCross = bound(priceBaseCross, 1, 1e27); | ||
| priceCrossQuote = bound(priceCrossQuote, 1, 1e27); | ||
|
|
||
| oracleBaseCross.setPrice(BASE, CROSS, priceBaseCross); | ||
| oracleQuoteCross.setPrice(CROSS, QUOTE, priceCrossQuote); | ||
|
|
||
| uint256 expectedOutAmount = inAmount * priceBaseCross / 1e18 * priceCrossQuote / 1e18; | ||
| assertEq(oracle.getQuote(inAmount, BASE, QUOTE), expectedOutAmount); | ||
| } | ||
|
|
||
| function test_GetQuote_Integrity_Inverse(uint256 inAmount, uint256 priceQuoteCross, uint256 priceCrossBase) | ||
| public | ||
| { | ||
| inAmount = bound(inAmount, 0, type(uint128).max); | ||
| priceQuoteCross = bound(priceQuoteCross, 1, 1e27); | ||
| priceCrossBase = bound(priceCrossBase, 1, 1e27); | ||
|
|
||
| oracleQuoteCross.setPrice(QUOTE, CROSS, priceQuoteCross); | ||
| oracleBaseCross.setPrice(CROSS, BASE, priceCrossBase); | ||
|
|
||
| uint256 expectedOutAmount = inAmount * priceQuoteCross / 1e18 * priceCrossBase / 1e18; | ||
| assertEq(oracle.getQuote(inAmount, QUOTE, BASE), expectedOutAmount); | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.