Oracle Security Module
1. Summary
The OSM
(Oracle Security Module) ensures that new price values propagated from medianizers are not taken up by the system until a specified delay has passed. Values are updated using updateResult
and read using read
and getResultWithValidity
. Each collateral type will have its own unique OSM
. Collateral type is defined as a specific asset type (whereas ETH-A
and ETH-B
refer to the same asset and thus have the same OSM
).
2. Contract Variables & Functions
Variables
authorizedAccounts[usr: address]
- addresses allowed to call authed functions.stopped
- flag that disables price feed updates if non-zeropriceSource
- address of medianizer that the OSM will read fromONE_HOUR
- 3600 secondsupdateDelay
- time delay betweenupdateResult
calls; defaults toONE_HOUR
lastUpdateTime
- time of last update (rounded down to nearest multiple ofupdateDelay
)currentFeed
-Feed
struct that holds the current price valuenextFeed
-Feed
struct that holds the next price value
Modifiers
isAuthorized
**** - checks whether an address is part ofauthorizedAddresses
(and thus can call authed functions).
Functions
addAuthorization
/removeAuthorization
- add or remove authorized users (via modifications to theauthorizedAccounts
mapping)stop()
/start()
- toggle whether the OSM price feed can be updated (by changing the value ofstopped
)changePriceSource(address)
- change data source for prices (by settingpriceSource
)changeDelay(uint16)
- change interval between price updates (by settingupdateDelay
)restartValue()
- similar tostop
, except it also setscurrentFeed
andnextFeed
to aFeed
struct with zero valuesgetResultWithValidity()
- returns the current feed value and a boolean indicating whether it is validgetNextResultWithValidity()
- returns the next feed value (i.e. the one that will become the current value upon the nextupdateResult()
call), and a boolean indicating whether it is validread()
- returns the current feed value; reverts if it was not set by some valid mechanismupdateResult()
- updates the current feed value and reads the next one
Data Structures
Feed
- struct used to store price feed data. Contains:value
- the feed valueisValid
- whethet the feed value is valid
3. Walkthrough
In order for the OSM
to work properly, an external actor must regularly call updateResult()
to update the current price and read the next one. The contract stores the timestamp of the last updateResult()
and will not allow another update until block.timestamp
is at least lastUpdateTime + updateDelay
. Values are read from the priceSource
. In case of an oracle attack, governance can call stop()
orrestartValue()
4. OSM Variations
SelfFundedOSM
This contract pulls funds from the StabilityFeeTreasury so it can reward addresses for callingupdateResult
.
ExternallyFundedOSM
This contract calls an FSMWrapper in order to reward addresses that call updateResult
.
5. Gotchas (Potential Sources of User Error)
N/A
6. Failure Modes (Bounds on Operating Conditions & External Risk Factors)
updateCollateralPrice()
is not called promptly, allowing malicious prices to be swiftly uptaken
For several reasons, updateCollateralPrice()
is always callable as soon as block.timestamp / updateDelay
increments, regardless of when the last updateCollateralPrice()
call occurred (because lastUpdateTime
is rounded down to the nearest multiple of updateDelay
). This means the contract does not actually guarantee that a time interval of at least updateDelay
seconds has passed since the last updateCollateralPrice()
call before the next one; rather this is only (approximately) guaranteed if the last updateCollateralPrice()
call occurred shortly after the previous increase of block.timestamp / updateDelay
. Thus, a malicious price value can be acknowledged by the system in a time potentially much less than updateDelay
.
This was a deliberate design decision. The arguments that favoured it, roughly speaking, are:
- Providing a predictable time at which Prot holders should check for evidence of oracle attacks (in practice,
updateDelay
is 1 hour, so checks must be performed at the top of the hour) - Allowing all OSMs to be reliably updated at the same time in a single transaction
The fact that updateCollateralPrice
is public, and thus callable by anyone, helps mitigate concerns, though it does not eliminate them. For example, network congestion could prevent anyone from successfully calling updateCollateralPrice()
for a period of time. If a Prot holder observes that updateCollateralPrice
has not been promptly called, the actions they can take include:
- Call
updateCollateralPrice()
themselves and decide if the next value is malicious or not - Call
stop()
orrestartValue()
(the former if onlynextFeed
is malicious; the latter if the malicious value is already incurrentFeed
) - Trigger emergency shutdown (if the integrity of the overall system has already been compromised or if it is believed the rogue oracle(s) cannot be fixed in a reasonable length of time)
In the future, the contract's logic may be tweaked to further mitigate this (e.g. by only allowing updateCollateralPrice()
calls in a short time window each updateDelay
period).
Authorization Attacks and Misconfigurations
Various damaging actions can be taken by authorized individuals or contracts, either maliciously or accidentally:
- Revoking access of core contracts to the methods that read values, causing mayhem as prices fail to update
- Completely revoking all access to the contract
- Changing
src
to either a malicious contract or to something that lacks agetResultWithValidity()
interface, causing transactions thatupdateCollateralPrice()
the affected OSM to revert - Calling disruptive functions like
stop
andrestartValue
inappropriately
The only solution to these issues is diligence and care regarding the authorizedAccounts
of the OSM.