Skip to content

Commit

Permalink
feat: smardex usdn native rate price feed (#1352)
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanJCasey authored Jan 29, 2025
1 parent 3dde2cb commit 85a5afe
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 1 deletion.
18 changes: 18 additions & 0 deletions contracts/external-interfaces/ISmarDexUsdnProtocol.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0

/*
This file is part of the Enzyme Protocol.
(c) Enzyme Foundation <security@enzyme.finance>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/

pragma solidity >=0.6.0 <0.9.0;

/// @title ISmarDexUsdnProtocol Interface
/// @author Enzyme Foundation <security@enzyme.finance>
interface ISmarDexUsdnProtocol {
function usdnPrice(uint128 _wstethInUsdRateWithWeiPrecision) external view returns (uint256 price_);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-3.0

/*
This file is part of the Enzyme Protocol.
(c) Enzyme Foundation <security@enzyme.finance>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/

pragma solidity 0.8.19;

import {IChainlinkAggregator} from "../../../../external-interfaces/IChainlinkAggregator.sol";
import {ISmarDexUsdnProtocol} from "../../../../external-interfaces/ISmarDexUsdnProtocol.sol";
import {PriceFeedHelpersLib} from "../utils/PriceFeedHelpersLib.sol";
import {RateAggregatorBase} from "./utils/RateAggregatorBase.sol";
import {RateUsdAggregatorBase} from "./utils/RateUsdAggregatorBase.sol";

/// @title SmarDexUsdnNativeRateUsdAggregator Contract
/// @author Enzyme Foundation <security@enzyme.finance>
/// @notice USD-quoted aggregator for SmarDex USDN using their native rate
contract SmarDexUsdnNativeRateUsdAggregator is RateUsdAggregatorBase {
/// @dev `USDN_RATES_PRECISION`: the precision used in USDN for both wsteth/usd rate input and USDN rate output
uint256 private constant USDN_RATES_PRECISION = 10 ** 18;

// Immutables: deployer-input
/// @dev `USDN_PROTOCOL`: the main SmarDex USDN protocol contract
ISmarDexUsdnProtocol public immutable USDN_PROTOCOL;
/// @dev `WSTETH_IN_USD_AGGREGATOR_ADDRESS`: the wstETH/USD aggregator to use as input for the USDN rate function
address public immutable WSTETH_IN_USD_AGGREGATOR_ADDRESS;
// Immutables: derived
/// @dev `WSTETH_IN_USD_AGGREGATOR_PRECISION`: the precision of WSTETH_IN_USD_AGGREGATOR_ADDRESS
uint256 private immutable WSTETH_IN_USD_AGGREGATOR_PRECISION;

constructor(address _usdnProtocolAddress, address _wstethInUsdAggregatorAddress)
RateUsdAggregatorBase(address(0), false)
{
USDN_PROTOCOL = ISmarDexUsdnProtocol(_usdnProtocolAddress);
WSTETH_IN_USD_AGGREGATOR_ADDRESS = _wstethInUsdAggregatorAddress;

WSTETH_IN_USD_AGGREGATOR_PRECISION =
PriceFeedHelpersLib.parsePrecisionFromChainlinkAggregator(_wstethInUsdAggregatorAddress);
}

//==================================================================================================================
// Required overrides: RateAggregatorBase
//==================================================================================================================

/// @inheritdoc RateAggregatorBase
/// @dev Returns the value of 1 unit of USDN:
/// - quoted in USD
/// - with 18-decimals of precision
/// - with the wstETH/USD aggregator's timestamp
function baseRate() public view override returns (uint256 rate_, uint256 ratePrecision_, uint256 timestamp_) {
// Get the wstETH/USD rate
(uint256 wstethInUsdRateInAggregatorPrecision, uint256 wstethInUsdRateTimestamp) =
PriceFeedHelpersLib.parseRateFromChainlinkAggregator(WSTETH_IN_USD_AGGREGATOR_ADDRESS);

// Convert wstETH/USD rate to USDN's expected precision
uint256 wstethInUsdRateWithWeiPrecision = PriceFeedHelpersLib.convertRatePrecision({
_rate: wstethInUsdRateInAggregatorPrecision,
_fromPrecision: WSTETH_IN_USD_AGGREGATOR_PRECISION,
_toPrecision: USDN_RATES_PRECISION
});

rate_ = USDN_PROTOCOL.usdnPrice({_wstethInUsdRateWithWeiPrecision: uint128(wstethInUsdRateWithWeiPrecision)});
ratePrecision_ = USDN_RATES_PRECISION;
timestamp_ = wstethInUsdRateTimestamp;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import {IntegrationTest} from "tests/bases/IntegrationTest.sol";
import {IChainlinkAggregator} from "tests/interfaces/external/IChainlinkAggregator.sol";

address constant USDN_PROTOCOL_ADDRESS = 0x656cB8C6d154Aad29d8771384089be5B5141f01a;
// Enzyme's `ChainlinkLikeWstethPriceFeed`
address constant WSTETH_ETH_AGGREGATOR = 0x92829C41115311cA43D5c9f722f0E9e7b9fcd30a;

contract Test is IntegrationTest {
IChainlinkAggregator usdnAggregator;
address wstethUsdAggregatorAddress;

function setUp() public override {
vm.createSelectFork("mainnet", ETHEREUM_BLOCK_LATEST);

// TODO: can replace with live address once it's deployed
// Deploy the wstETH/USD aggregator
wstethUsdAggregatorAddress = __deployWstethInUsdAggregator();

// Deploy the USDN aggregator
usdnAggregator = __deployUsdnAggregator(wstethUsdAggregatorAddress);
}

// DEPLOYMENT HELPERS

function __deployUsdnAggregator(address _wstethInUsdAggregatorAddress)
private
returns (IChainlinkAggregator usdnAggregator_)
{
bytes memory args = abi.encode(USDN_PROTOCOL_ADDRESS, _wstethInUsdAggregatorAddress);

return IChainlinkAggregator(deployCode("SmarDexUsdnNativeRateUsdAggregator.sol", args));
}

function __deployWstethInUsdAggregator() private returns (address wstethAggregatorAddress_) {
// Convert wstETH from ETH to USD quote using ETH/USD
return deployCode(
"ConvertedQuoteAggregator.sol",
abi.encode(CHAINLINK_AGGREGATOR_DECIMALS_USD, ETHEREUM_ETH_USD_AGGREGATOR, false, WSTETH_ETH_AGGREGATOR)
);
}

// TESTS

function test_decimals_success() public {
assertEq(usdnAggregator.decimals(), CHAINLINK_AGGREGATOR_DECIMALS_USD, "Incorrect decimals");
}

function test_latestRoundData_success() public {
(, uint256 wstethUsdTimestamp) = parseRateFromChainlinkAggregator(wstethUsdAggregatorAddress);
(uint256 usdnRate, uint256 usdnTimestamp) = parseRateFromChainlinkAggregator(address(usdnAggregator));

// Should be very close to 1, unless the rate starts to depeg
uint256 expectedRate = 1e8; // "1" in USD aggregator precision
uint256 percentTolerance = WEI_ONE_PERCENT; // 1% tolerance
assertApproxEqRel(usdnRate, expectedRate, percentTolerance, "Incorrect rate");
// Timestamp should be that of the wsteth aggregator
assertEq(usdnTimestamp, wstethUsdTimestamp, "Incorrect timestamp");
}
}
2 changes: 1 addition & 1 deletion tests/utils/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ abstract contract Constants {
// expected exchange rates, etc.
// `ETHEREUM_BLOCK_LATEST` can be increased as-needed, and should be used in all tests
// that should generally continue to pass regardless of block.
uint256 internal constant ETHEREUM_BLOCK_LATEST = 21487350; // Dec 26th, 2024
uint256 internal constant ETHEREUM_BLOCK_LATEST = 21710000; // Jan 26th, 2025
uint256 internal constant ETHEREUM_BLOCK_TIME_SENSITIVE = 20711624; // Sep 9th, 2024
uint256 internal constant ETHEREUM_BLOCK_TIME_SENSITIVE_ONE_INCH_V5 = 19518890; // March 26th, 2024
uint256 internal constant ETHEREUM_BLOCK_TIME_SENSITIVE_PENDLE = 20100000; // June 15th, 2024
Expand Down
11 changes: 11 additions & 0 deletions tests/utils/core/AssetUniverseUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ abstract contract AssetUniverseUtils is CoreUtilsBase {
return IChainlinkAggregator(deployCode("UsdEthSimulatedAggregator.sol", abi.encode(_ethUsdAggregatorAddress)));
}

function parseRateFromChainlinkAggregator(address _aggregatorAddress)
internal
view
returns (uint256 rate_, uint256 timestamp_)
{
int256 answer;
(, answer,, timestamp_,) = IChainlinkAggregator(_aggregatorAddress).latestRoundData();

rate_ = uint256(answer);
}

// ASSET REGISTRATION

function addDerivative(
Expand Down

0 comments on commit 85a5afe

Please sign in to comment.