From bb8b02a2552eaf8a3d6508cf6c63b56f136c5e81 Mon Sep 17 00:00:00 2001 From: Dima Lekhovitsky Date: Tue, 23 Jul 2024 12:59:58 +0300 Subject: [PATCH 1/3] fix: nits --- contracts/oracles/balancer/BPTStablePriceFeed.sol | 2 +- contracts/oracles/balancer/BPTWeightedPriceFeed.sol | 2 +- contracts/oracles/curve/CurveCryptoLPPriceFeed.sol | 2 +- contracts/oracles/updatable/PythPriceFeed.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/oracles/balancer/BPTStablePriceFeed.sol b/contracts/oracles/balancer/BPTStablePriceFeed.sol index 414c3aa..c90bef8 100644 --- a/contracts/oracles/balancer/BPTStablePriceFeed.sol +++ b/contracts/oracles/balancer/BPTStablePriceFeed.sol @@ -12,7 +12,7 @@ import {IBalancerStablePool} from "../../interfaces/balancer/IBalancerStablePool /// @dev Similarly to Curve stableswap, aggregate function is minimum of underlying tokens prices contract BPTStablePriceFeed is LPPriceFeed { uint256 public constant override version = 3_10; - bytes32 public constant contractType = "PF_BALANCER_STABLE_LP_ORACLE"; + bytes32 public constant override contractType = "PF_BALANCER_STABLE_LP_ORACLE"; uint8 public immutable numAssets; diff --git a/contracts/oracles/balancer/BPTWeightedPriceFeed.sol b/contracts/oracles/balancer/BPTWeightedPriceFeed.sol index 5253b66..e27c404 100644 --- a/contracts/oracles/balancer/BPTWeightedPriceFeed.sol +++ b/contracts/oracles/balancer/BPTWeightedPriceFeed.sol @@ -32,7 +32,7 @@ contract BPTWeightedPriceFeed is LPPriceFeed { using FixedPoint for uint256; uint256 public constant override version = 3_10; - bytes32 public constant contractType = "PF_BALANCER_WEIGHTED_LP_ORACLE"; + bytes32 public constant override contractType = "PF_BALANCER_WEIGHTED_LP_ORACLE"; /// @notice Balancer vault address address public immutable vault; diff --git a/contracts/oracles/curve/CurveCryptoLPPriceFeed.sol b/contracts/oracles/curve/CurveCryptoLPPriceFeed.sol index 72c975a..3da69fa 100644 --- a/contracts/oracles/curve/CurveCryptoLPPriceFeed.sol +++ b/contracts/oracles/curve/CurveCryptoLPPriceFeed.sol @@ -18,7 +18,7 @@ contract CurveCryptoLPPriceFeed is LPPriceFeed { using FixedPoint for uint256; uint256 public constant override version = 3_10; - bytes32 public constant contractType = "PF_CURVE_CRYPTO_LP_ORACLE"; + bytes32 public constant override contractType = "PF_CURVE_CRYPTO_LP_ORACLE"; uint16 public immutable nCoins; diff --git a/contracts/oracles/updatable/PythPriceFeed.sol b/contracts/oracles/updatable/PythPriceFeed.sol index 44df88b..7d579b2 100644 --- a/contracts/oracles/updatable/PythPriceFeed.sol +++ b/contracts/oracles/updatable/PythPriceFeed.sol @@ -29,7 +29,7 @@ contract PythPriceFeed is IUpdatablePriceFeed { // --------------- // uint256 public constant override version = 3_10; - bytes32 public constant contractType = "PF_PYTH_ORACLE"; + bytes32 public constant override contractType = "PF_PYTH_ORACLE"; uint8 public constant override decimals = 8; bool public constant override skipPriceCheck = false; From 3624e3e3019724c4e05138f0a0fc060d4e77e701 Mon Sep 17 00:00:00 2001 From: Dima Lekhovitsky Date: Tue, 23 Jul 2024 13:01:10 +0300 Subject: [PATCH 2/3] feat: read `baseToken` from mellow vault directly --- .../interfaces/mellow/IMellowChainlinkOracle.sol | 8 ++++++++ contracts/interfaces/mellow/IMellowVault.sol | 4 ++++ .../mellow/IMellowVaultConfigurator.sol | 10 ++++++++++ contracts/oracles/mellow/MellowLRTPriceFeed.sol | 10 ++-------- .../mocks/mellow/MellowChainlinkOracleMock.sol | 14 ++++++++++++++ .../mocks/mellow/MellowVaultConfiguratorMock.sol | 15 +++++++++++++++ contracts/test/mocks/mellow/MellowVaultMock.sol | 7 ++++++- contracts/test/suites/PriceFeedDeployer.sol | 3 +-- .../unit/mellow/MellowLRTPriceFeed.unit.t.sol | 11 +++++++++-- 9 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 contracts/interfaces/mellow/IMellowChainlinkOracle.sol create mode 100644 contracts/interfaces/mellow/IMellowVaultConfigurator.sol create mode 100644 contracts/test/mocks/mellow/MellowChainlinkOracleMock.sol create mode 100644 contracts/test/mocks/mellow/MellowVaultConfiguratorMock.sol diff --git a/contracts/interfaces/mellow/IMellowChainlinkOracle.sol b/contracts/interfaces/mellow/IMellowChainlinkOracle.sol new file mode 100644 index 0000000..155ec47 --- /dev/null +++ b/contracts/interfaces/mellow/IMellowChainlinkOracle.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2024. +pragma solidity ^0.8.23; + +interface IMellowChainlinkOracle { + function baseTokens(address vault) external view returns (address); +} diff --git a/contracts/interfaces/mellow/IMellowVault.sol b/contracts/interfaces/mellow/IMellowVault.sol index 3189638..a23b455 100644 --- a/contracts/interfaces/mellow/IMellowVault.sol +++ b/contracts/interfaces/mellow/IMellowVault.sol @@ -3,6 +3,8 @@ // (c) Gearbox Foundation, 2024. pragma solidity ^0.8.23; +import {IMellowVaultConfigurator} from "./IMellowVaultConfigurator.sol"; + interface IMellowVault { struct ProcessWithdrawalsStack { address[] tokens; @@ -17,4 +19,6 @@ interface IMellowVault { } function calculateStack() external view returns (ProcessWithdrawalsStack memory s); + + function configurator() external view returns (IMellowVaultConfigurator); } diff --git a/contracts/interfaces/mellow/IMellowVaultConfigurator.sol b/contracts/interfaces/mellow/IMellowVaultConfigurator.sol new file mode 100644 index 0000000..54b09d4 --- /dev/null +++ b/contracts/interfaces/mellow/IMellowVaultConfigurator.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2024. +pragma solidity ^0.8.23; + +import {IMellowChainlinkOracle} from "./IMellowChainlinkOracle.sol"; + +interface IMellowVaultConfigurator { + function priceOracle() external view returns (IMellowChainlinkOracle); +} diff --git a/contracts/oracles/mellow/MellowLRTPriceFeed.sol b/contracts/oracles/mellow/MellowLRTPriceFeed.sol index 331523b..ead3e57 100644 --- a/contracts/oracles/mellow/MellowLRTPriceFeed.sol +++ b/contracts/oracles/mellow/MellowLRTPriceFeed.sol @@ -16,16 +16,10 @@ contract MellowLRTPriceFeed is SingleAssetLPPriceFeed { /// @dev Amount of base token comprising a single unit (accounting for decimals) uint256 immutable _baseTokenUnit; - constructor( - address _acl, - uint256 lowerBound, - address _vault, - address _priceFeed, - uint32 _stalenessPeriod, - address baseToken - ) + constructor(address _acl, uint256 lowerBound, address _vault, address _priceFeed, uint32 _stalenessPeriod) SingleAssetLPPriceFeed(_acl, _vault, _vault, _priceFeed, _stalenessPeriod) // U:[MEL-1] { + address baseToken = IMellowVault(_vault).configurator().priceOracle().baseTokens(_vault); _baseTokenUnit = 10 ** ERC20(baseToken).decimals(); _setLimiter(lowerBound); // U:[MEL-1] } diff --git a/contracts/test/mocks/mellow/MellowChainlinkOracleMock.sol b/contracts/test/mocks/mellow/MellowChainlinkOracleMock.sol new file mode 100644 index 0000000..95d1d92 --- /dev/null +++ b/contracts/test/mocks/mellow/MellowChainlinkOracleMock.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2024. +pragma solidity ^0.8.23; + +import {IMellowChainlinkOracle} from "../../../interfaces/mellow/IMellowChainlinkOracle.sol"; + +contract MellowChainlinkOracleMock is IMellowChainlinkOracle { + mapping(address vault => address) public baseTokens; + + function setBaseToken(address vault, address baseToken) external { + baseTokens[vault] = baseToken; + } +} diff --git a/contracts/test/mocks/mellow/MellowVaultConfiguratorMock.sol b/contracts/test/mocks/mellow/MellowVaultConfiguratorMock.sol new file mode 100644 index 0000000..03bc5b9 --- /dev/null +++ b/contracts/test/mocks/mellow/MellowVaultConfiguratorMock.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2024. +pragma solidity ^0.8.23; + +import {IMellowChainlinkOracle} from "../../../interfaces/mellow/IMellowChainlinkOracle.sol"; +import {IMellowVaultConfigurator} from "../../../interfaces/mellow/IMellowVaultConfigurator.sol"; + +contract MellowVaultConfiguratorMock is IMellowVaultConfigurator { + IMellowChainlinkOracle public priceOracle; + + constructor(IMellowChainlinkOracle priceOracle_) { + priceOracle = priceOracle_; + } +} diff --git a/contracts/test/mocks/mellow/MellowVaultMock.sol b/contracts/test/mocks/mellow/MellowVaultMock.sol index a783520..72feb82 100644 --- a/contracts/test/mocks/mellow/MellowVaultMock.sol +++ b/contracts/test/mocks/mellow/MellowVaultMock.sol @@ -4,11 +4,16 @@ pragma solidity ^0.8.23; import {IMellowVault} from "../../../interfaces/mellow/IMellowVault.sol"; +import {IMellowVaultConfigurator} from "../../../interfaces/mellow/IMellowVaultConfigurator.sol"; contract MellowVaultMock is IMellowVault { IMellowVault.ProcessWithdrawalsStack stack; - constructor() {} + IMellowVaultConfigurator public configurator; + + constructor(IMellowVaultConfigurator configurator_) { + configurator = configurator_; + } function setStack(uint256 totalValue, uint256 totalSupply) external { stack.totalValue = totalValue; diff --git a/contracts/test/suites/PriceFeedDeployer.sol b/contracts/test/suites/PriceFeedDeployer.sol index 9bceeff..c69c1b7 100644 --- a/contracts/test/suites/PriceFeedDeployer.sol +++ b/contracts/test/suites/PriceFeedDeployer.sol @@ -664,8 +664,7 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { lowerBound, token, _getDeployedFeed(underlying, mellowLRTPriceFeeds[i].reserve), - _getDeployedStalenessPeriod(underlying, mellowLRTPriceFeeds[i].reserve), - underlying + _getDeployedStalenessPeriod(underlying, mellowLRTPriceFeeds[i].reserve) ) ); diff --git a/contracts/test/unit/mellow/MellowLRTPriceFeed.unit.t.sol b/contracts/test/unit/mellow/MellowLRTPriceFeed.unit.t.sol index 53fbf1d..5600d68 100644 --- a/contracts/test/unit/mellow/MellowLRTPriceFeed.unit.t.sol +++ b/contracts/test/unit/mellow/MellowLRTPriceFeed.unit.t.sol @@ -7,7 +7,9 @@ import {IMellowVault} from "../../../interfaces/mellow/IMellowVault.sol"; import {PriceFeedUnitTestHelper} from "../PriceFeedUnitTestHelper.sol"; +import {MellowChainlinkOracleMock} from "../../mocks/mellow/MellowChainlinkOracleMock.sol"; import {MellowVaultMock} from "../../mocks/mellow/MellowVaultMock.sol"; +import {MellowVaultConfiguratorMock} from "../../mocks/mellow/MellowVaultConfiguratorMock.sol"; import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; import {MellowLRTPriceFeed} from "../../../oracles/mellow/MellowLRTPriceFeed.sol"; @@ -23,10 +25,15 @@ contract MellowLRTPriceFeedUnitTest is PriceFeedUnitTestHelper { _setUp(); asset = new ERC20Mock("Test Token", "TEST", 18); - vault = new MellowVaultMock(); + + MellowChainlinkOracleMock chainlinkOracle = new MellowChainlinkOracleMock(); + MellowVaultConfiguratorMock vaultConfigurator = new MellowVaultConfiguratorMock(chainlinkOracle); + vault = new MellowVaultMock(vaultConfigurator); vault.setStack(1.2e18, 1e18); + chainlinkOracle.setBaseToken(address(vault), address(asset)); + priceFeed = new MellowLRTPriceFeed( - address(addressProvider), 1.2e18, address(vault), address(underlyingPriceFeed), 1 days, address(asset) + address(addressProvider), 1.2e18, address(vault), address(underlyingPriceFeed), 1 days ); } From a7447addd7efa6e9e40fff5c78513a45731e0175 Mon Sep 17 00:00:00 2001 From: Dima Lekhovitsky Date: Wed, 24 Jul 2024 11:49:21 +0300 Subject: [PATCH 3/3] fix: improved `RedstonePriceFeed` errors --- .../oracles/updatable/RedstonePriceFeed.sol | 19 ++++++++++--------- .../updatable/RedstonePriceFeed.unit.t.sol | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/contracts/oracles/updatable/RedstonePriceFeed.sol b/contracts/oracles/updatable/RedstonePriceFeed.sol index 49ffb04..d1a75f8 100644 --- a/contracts/oracles/updatable/RedstonePriceFeed.sol +++ b/contracts/oracles/updatable/RedstonePriceFeed.sol @@ -82,12 +82,14 @@ contract RedstonePriceFeed is IUpdatablePriceFeed, RedstoneConsumerNumericBase { /// @notice Thrown when the provided set of signers contains duplicates error DuplicateSignersException(); - /// @notice Thrown when attempting to push an update with the payload that is older than the last - /// update payload, or too far from the current block timestamp - error RedstonePayloadTimestampIncorrect(); + /// @notice Thrown when expected payload timestamp is too far ahead in the future + error PayloadTimestampTooFarAheadException(); + + /// @notice Thrown when expected payload timestamp is too far behind in the past + error PayloadTimestampTooFarBehindException(); /// @notice Thrown when data package timestamp is not equal to expected payload timestamp - error DataPackageTimestampIncorrect(); + error IncorrectDataPackageTimestampException(); // ----------- // // CONSTRUCTOR // @@ -223,20 +225,19 @@ contract RedstonePriceFeed is IUpdatablePriceFeed, RedstoneConsumerNumericBase { uint256 receivedTimestampSeconds = receivedTimestampMilliseconds / 1000; if (receivedTimestampSeconds != lastPayloadTimestamp) { - revert DataPackageTimestampIncorrect(); // U:[RPF-3] + revert IncorrectDataPackageTimestampException(); // U:[RPF-3] } } - /// @dev Validates that the expected payload timestamp is not older than the last payload's, - /// and not too far from the current block's + /// @dev Validates that the expected payload timestamp is not too far ahead or behind of the current block timestamp /// @param expectedPayloadTimestamp Timestamp expected to be in all of the incoming payload's packages function _validateExpectedPayloadTimestamp(uint256 expectedPayloadTimestamp) internal view { if ((block.timestamp < expectedPayloadTimestamp)) { if ((expectedPayloadTimestamp - block.timestamp) > MAX_DATA_TIMESTAMP_AHEAD_SECONDS) { - revert RedstonePayloadTimestampIncorrect(); // U:[RPF-9] + revert PayloadTimestampTooFarAheadException(); // U:[RPF-9] } } else if ((block.timestamp - expectedPayloadTimestamp) > MAX_DATA_TIMESTAMP_DELAY_SECONDS) { - revert RedstonePayloadTimestampIncorrect(); // U:[RPF-9] + revert PayloadTimestampTooFarBehindException(); // U:[RPF-9] } } } diff --git a/contracts/test/unit/updatable/RedstonePriceFeed.unit.t.sol b/contracts/test/unit/updatable/RedstonePriceFeed.unit.t.sol index ec0df2b..8858760 100644 --- a/contracts/test/unit/updatable/RedstonePriceFeed.unit.t.sol +++ b/contracts/test/unit/updatable/RedstonePriceFeed.unit.t.sol @@ -153,7 +153,7 @@ contract RedstonePriceFeedUnitTest is TestHelper, RedstoneConstants { bytes memory data = abi.encode(expectedPayloadTimestamp, payload); - vm.expectRevert(RedstonePriceFeed.DataPackageTimestampIncorrect.selector); + vm.expectRevert(RedstonePriceFeed.IncorrectDataPackageTimestampException.selector); pf.updatePrice(data); } @@ -264,7 +264,7 @@ contract RedstonePriceFeedUnitTest is TestHelper, RedstoneConstants { bytes memory data = abi.encode(expectedPayloadTimestamp, payload); - vm.expectRevert(RedstonePriceFeed.RedstonePayloadTimestampIncorrect.selector); + vm.expectRevert(RedstonePriceFeed.PayloadTimestampTooFarBehindException.selector); pf.updatePrice(data); @@ -276,7 +276,7 @@ contract RedstonePriceFeedUnitTest is TestHelper, RedstoneConstants { data = abi.encode(expectedPayloadTimestamp, payload); - vm.expectRevert(RedstonePriceFeed.RedstonePayloadTimestampIncorrect.selector); + vm.expectRevert(RedstonePriceFeed.PayloadTimestampTooFarAheadException.selector); pf.updatePrice(data); }