From 1974f3b08fdb080e0d821844ca4f397c1103f0ac Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 9 Oct 2025 16:41:01 -0400 Subject: [PATCH 1/2] improve oracle documentation --- .../facade/oracles/ExchangeRateOracle.sol | 19 ++++++++++---- .../facade/oracles/ReferenceRateOracle.sol | 25 ++++++++----------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/contracts/facade/oracles/ExchangeRateOracle.sol b/contracts/facade/oracles/ExchangeRateOracle.sol index 185678046..4bd78c935 100644 --- a/contracts/facade/oracles/ExchangeRateOracle.sol +++ b/contracts/facade/oracles/ExchangeRateOracle.sol @@ -10,16 +10,24 @@ import { IRToken } from "../../interfaces/IRToken.sol"; * @title ExchangeRateOracle * @notice An immutable Exchange Rate Oracle for an RToken (eg: ETH+/ETH) * + * ::Assumption:: + * Constant basket target definition of only a single target unit, of the correct magnitude. + * For example, an ETH-pegged RToken should define the basket as 1 ETH, and a USD-pegged RToken + * should define the basket as 1 USD. The basket target units should not be redefined after. + * * ::Notice:: - * The oracle does not call refresh() on the RToken or the underlying assets, so the price can be - * stale. This is generally not an issue for active RTokens as they are refreshed often by other + * The oracle does not call refresh() on the RToken, so the exchange rate can be stale. + * This is generally not an issue for active RTokens as they are refreshed by other * protocol operations, however do keep this in mind when using this for low-activity RTokens. + * This can lead to the exchange rate being underestimated by the amount of unrealized RToken + * melting (exchange-rate appreciation). * - * If you need the freshest possible price, consider using RTokenAsset.latestPrice() instead, - * however it is a mutator function instead of a view-only function hence not compatible with + * If you need a fresher exchange-rate, consider calling `furnace.melt()` or + * `RTokenAsset.refresh()`. Note these are mutators, and hence not compatible with * Chainlink style interfaces. * - * ::Warning:: In the event of an RToken taking a loss in excess of the StRSR overcollateralization + * ::Warning:: + * In the event of an RToken taking a loss in excess of the StRSR overcollateralization * layer, the devaluation will not be reflected until the RToken is done trading. This causes * the exchange rate to be too high during the rebalancing phase. If the exchange rate is relied * upon naively, then it could be misleading. @@ -30,6 +38,7 @@ import { IRToken } from "../../interfaces/IRToken.sol"; * * However, note that `fullyCollateralized()` is extremely gas-costly. We recommend executing * the function off-chain. `status()` is cheap and more reasonable to be called on-chain. + * */ contract ExchangeRateOracle is IExchangeRateOracle { error ZeroAddress(); diff --git a/contracts/facade/oracles/ReferenceRateOracle.sol b/contracts/facade/oracles/ReferenceRateOracle.sol index a32dc47f8..833e3694c 100644 --- a/contracts/facade/oracles/ReferenceRateOracle.sol +++ b/contracts/facade/oracles/ReferenceRateOracle.sol @@ -13,23 +13,18 @@ import { IAsset } from "../../interfaces/IAsset.sol"; * @notice An immutable Reference Rate Oracle for an RToken (eg: ETH+/USD) * * Composes oracles used by the protocol internally to calculate the reference price of an RToken, - * in UoA terms, usually USD. + * in UoA terms, usually USD. Inherits the deviations of the underlying oracles in proportion to + * their presence in the basket by value. * * ::Notice:: * The oracle does not call refresh() on the RToken or the underlying assets, so the price can be * stale. This is generally not an issue for active RTokens as they are refreshed often by other * protocol operations, however do keep this in mind when using this for low-activity RTokens. * - * If you need the freshest possible price, consider using RTokenAsset.latestPrice() instead, - * however it is a mutator function instead of a view-only function hence not compatible with - * Chainlink style interfaces, and additionally can revert. - * - * As a consumer of this oracle, you may want to guard against this case by monitoring: - * `basketHandler.status() == 0 && basketHandler.fullyCollateralized()` - * where `basketHandler` can be safely cached from `rToken.main().basketHandler()`. - * - * However, note that `fullyCollateralized()` is extremely gas-costly. We recommend executing - * the function off-chain. `status()` is cheap and more reasonable to be called on-chain. + * If you need a fresher price, consider using `RTokenAsset.latestPrice()` instead. Precede with a + * call to `RTokenAsset.forceUpdatePrice()` if a 15-minute caching period is not acceptable. + * However, note both these functions are mutators, and hence not compatible with + * Chainlink style interfaces (and can revert). */ contract ReferenceRateOracle is IExchangeRateOracle { error ZeroAddress(); @@ -49,7 +44,7 @@ contract ReferenceRateOracle is IExchangeRateOracle { } function decimals() external view override returns (uint8) { - return rToken.decimals(); + return 18; } function description() external view override returns (string memory) { @@ -67,7 +62,7 @@ contract ReferenceRateOracle is IExchangeRateOracle { require(lower != 0 && upper < FIX_MAX, "invalid price"); /** - * In >=4.2.0 (not yet deployed) there is a feature called the "issuance premium", + * In >=4.0.0 there is a feature called the "issuance premium", * which if enabled, will cause the high price to remain relatively static, * even when an RToken collateral is under peg. * @@ -77,8 +72,8 @@ contract ReferenceRateOracle is IExchangeRateOracle { * Using the average of the issuance redemption cost in this case can result in a quantity * biased upwards. * - * If you need the *lowest* possible price the RToken can have, do not use this approach. - * Instead, use the `lower` price directly. Include our check above that `low > 0`. + * If you need the redemption value of the RToken, do not use this approach. + * Instead, use the `lower` price directly. Include our check above that `lower != 0`. */ return (lower + upper) / 2; From 54b09178f851cc90df6f3084fdd8e59de7e38f22 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 10 Oct 2025 23:16:22 -0400 Subject: [PATCH 2/2] reference RTokenAsset.tryPrice() --- contracts/facade/oracles/ReferenceRateOracle.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/facade/oracles/ReferenceRateOracle.sol b/contracts/facade/oracles/ReferenceRateOracle.sol index 833e3694c..cd5536453 100644 --- a/contracts/facade/oracles/ReferenceRateOracle.sol +++ b/contracts/facade/oracles/ReferenceRateOracle.sol @@ -14,7 +14,8 @@ import { IAsset } from "../../interfaces/IAsset.sol"; * * Composes oracles used by the protocol internally to calculate the reference price of an RToken, * in UoA terms, usually USD. Inherits the deviations of the underlying oracles in proportion to - * their presence in the basket by value. + * their presence in the basket by value. Refer to `RTokenAsset.tryPrice()` for more detailed + * information about the pricing method. * * ::Notice:: * The oracle does not call refresh() on the RToken or the underlying assets, so the price can be