From 8f93b2e7ef5563fe0b058fd73f297c3aabc7c057 Mon Sep 17 00:00:00 2001 From: "chiaki.hoshin" Date: Fri, 5 Apr 2024 00:19:54 +0800 Subject: [PATCH] add fxusd nav adapter --- foundry.toml | 2 +- .../FxUSDNetAssetValueChainlinkAdapter.sol | 38 +++++++++++++++ src/fxusd-nav-adapter/interfaces/IFxUSD.sol | 7 +++ .../MinimalAggregatorV3Interface.sol | 17 +++++++ ...FxUSDNetAssetValueChainlinkAdapterTest.sol | 46 +++++++++++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/fxusd-nav-adapter/FxUSDNetAssetValueChainlinkAdapter.sol create mode 100644 src/fxusd-nav-adapter/interfaces/IFxUSD.sol create mode 100644 src/fxusd-nav-adapter/interfaces/MinimalAggregatorV3Interface.sol create mode 100644 test/FxUSDNetAssetValueChainlinkAdapterTest.sol diff --git a/foundry.toml b/foundry.toml index fad254c..5261dba 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] optimizer_runs = 999999 # Etherscan does not support verifying contracts with more optimizer runs. via_ir = true -evm_version = "paris" +evm_version = "shanghai" # use shanghai to pass tests in FxUSDNetAssetValueChainlinkAdapterTest [profile.default.fmt] wrap_comments = true diff --git a/src/fxusd-nav-adapter/FxUSDNetAssetValueChainlinkAdapter.sol b/src/fxusd-nav-adapter/FxUSDNetAssetValueChainlinkAdapter.sol new file mode 100644 index 0000000..a7225ba --- /dev/null +++ b/src/fxusd-nav-adapter/FxUSDNetAssetValueChainlinkAdapter.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.21; + +import {IFxUSD} from "./interfaces/IFxUSD.sol"; +import {MinimalAggregatorV3Interface} from "./interfaces/MinimalAggregatorV3Interface.sol"; + +/// @title FxUSDNetAssetValueChainlinkAdapter +/// @author Aladdin DAO +/// @custom:contact security@morpho.org +/// @notice fxUSD net asset value price feed. +/// @dev This contract should only be deployed on Ethereum and used as a price feed for Morpho oracles. +contract FxUSDNetAssetValueChainlinkAdapter is MinimalAggregatorV3Interface { + /// @inheritdoc MinimalAggregatorV3Interface + // @dev The calculated price has 18 decimals precision, whatever the value of `decimals`. + uint8 public constant decimals = 18; + + /// @notice The description of the price feed. + string public constant description = "fxUSD net asset value"; + + /// @notice The address of fxUSD on Ethereum. + IFxUSD public immutable fxUSD; + + constructor(IFxUSD _fxUSD) { + fxUSD = _fxUSD; + } + + /// @inheritdoc MinimalAggregatorV3Interface + /// @dev Returns zero for roundId, startedAt, updatedAt and answeredInRound. + /// @dev Silently overflows if `nav`'s return value is greater than `type(int256).max`. + function latestRoundData() + external + view + returns (uint80, int256, uint256, uint256, uint80) + { + // It is assumed that `fxUSD.nav()` returns a price with 18 decimals precision. + return (0, int256(fxUSD.nav()), 0, 0, 0); + } +} diff --git a/src/fxusd-nav-adapter/interfaces/IFxUSD.sol b/src/fxusd-nav-adapter/interfaces/IFxUSD.sol new file mode 100644 index 0000000..a41d149 --- /dev/null +++ b/src/fxusd-nav-adapter/interfaces/IFxUSD.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +interface IFxUSD { + /// @notice Return the nav of fxUSD. + function nav() external view returns (uint256); +} diff --git a/src/fxusd-nav-adapter/interfaces/MinimalAggregatorV3Interface.sol b/src/fxusd-nav-adapter/interfaces/MinimalAggregatorV3Interface.sol new file mode 100644 index 0000000..22887af --- /dev/null +++ b/src/fxusd-nav-adapter/interfaces/MinimalAggregatorV3Interface.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.0; + +/// @dev Inspired by +/// https://github.com/smartcontractkit/chainlink/blob/master/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol +/// @dev This is the minimal feed interface required by `MorphoChainlinkOracleV2`. +interface MinimalAggregatorV3Interface { + /// @notice Returns the precision of the feed. + function decimals() external view returns (uint8); + + /// @notice Returns Chainlink's `latestRoundData` return values. + /// @notice Only the `answer` field is used by `MorphoChainlinkOracleV2`. + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); +} diff --git a/test/FxUSDNetAssetValueChainlinkAdapterTest.sol b/test/FxUSDNetAssetValueChainlinkAdapterTest.sol new file mode 100644 index 0000000..ddca739 --- /dev/null +++ b/test/FxUSDNetAssetValueChainlinkAdapterTest.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import "./helpers/Constants.sol"; +import "../lib/forge-std/src/Test.sol"; +import {MorphoChainlinkOracleV2} from "../src/morpho-chainlink/MorphoChainlinkOracleV2.sol"; +import "../src/fxusd-nav-adapter/FxUSDNetAssetValueChainlinkAdapter.sol"; + +contract FxUSDNetAssetValueChainlinkAdapterTest is Test { + IFxUSD internal constant fxUSD = IFxUSD(0x085780639CC2cACd35E474e71f4d000e2405d8f6); + + FxUSDNetAssetValueChainlinkAdapter internal adapter; + MorphoChainlinkOracleV2 internal morphoOracle; + + function setUp() public { + vm.createSelectFork(vm.envString("ETH_RPC_URL")); + require(block.chainid == 1, "chain isn't Ethereum"); + adapter = new FxUSDNetAssetValueChainlinkAdapter(fxUSD); + morphoOracle = new MorphoChainlinkOracleV2( + vaultZero, 1, AggregatorV3Interface(address(adapter)), feedZero, 18, vaultZero, 1, feedZero, feedZero, 18 + ); + } + + function testDecimals() public { + assertEq(adapter.decimals(), uint8(18)); + } + + function testDescription() public { + assertEq(adapter.description(), "fxUSD net asset value"); + } + + function testLatestRoundData() public { + (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = + adapter.latestRoundData(); + assertEq(roundId, 0); + assertEq(uint256(answer), fxUSD.nav()); + assertEq(startedAt, 0); + assertEq(updatedAt, 0); + assertEq(answeredInRound, 0); + } + + function testOracleFxUSDNav() public { + (, int256 expectedPrice,,,) = adapter.latestRoundData(); + assertEq(morphoOracle.price(), uint256(expectedPrice) * 10 ** (36 + 18 - 18 - 18)); + } +}