OSM (Oracle Security Module)
The OSM (named via acronym from “Oracle Security Module”) ensures that new price values propagated from the Oracles are not taken up by the system until a specified delay has passed. Values are read from any contract that has the read() and peek() interfaces via the poke() method; the read() and peek() methods will give the current value of the price feed, and other contracts must be whitelisted in order to call these. An OSM contract can only read from a single price feed, so in practice one OSM contract must be deployed per collateral type.
Key Mechanisms & Concepts
Section titled “Key Mechanisms & Concepts”The central mechanism of the OSM is to periodically feed a delayed price into the MCD system for a particular collateral type. For this to work properly, an external actor must regularly call the poke() method to update the current price and read the next price. The contract tracks the time of the last call to poke() in the zzz variable (rounded down to the nearest multiple of hop, and will not allow poke() to be called again until block.timestamp is at least zzz+hop. Values are read from a designated DSValue contract (its address is stored in src). The purpose of this delayed updating mechanism is to ensure that there is time to detect and react to an Oracle attack (e.g. setting a collateral’s price to zero). Responses to this include calling stop() or void(), or triggering Emergency Shutdown.
Other contracts, if whitelisted, may inspect the cur value via the peek() and read() methods (peek() returns an additional boolean indicating whether the value has actually been set; read() reverts if the value has not been set). The nxt value may be inspected via peep().
The contract uses a dual-tier authorization scheme: addresses mapped to 1 in wards may start and stop, set the src, call void(), and add new readers; addresses mapped to 1 in buds may call peek(), peep(), and read().
Gotchas (Potential Sources of User Error)
Section titled “Gotchas (Potential Sources of User Error)”Confusing peek() for peep() (or vice-versa)
Section titled “Confusing peek() for peep() (or vice-versa)”The names of these methods differ by only a single character and in current linguistic usage, both “peek” and “peep” have essentially the same meaning. This makes it easy for a developer to confuse the two and call the wrong one. The effects of such an error are naturally context-dependent, but could e.g. completely invalidate the purpose of the OSM if the peep() is called where instead peek() should be used. A mnemonic to help distinguish them: “since ‘k’ comes before ‘p’ in the English alphabet, the value returned by peek() comes before the value returned by peep() in chronological order”. Or: “peek() returns the current value”.
Failure Modes (Bounds on Operating Conditions & External Risk Factors)
Section titled “Failure Modes (Bounds on Operating Conditions & External Risk Factors)”poke() is not called promptly, allowing malicious prices to be swiftly uptaken
Section titled “poke() is not called promptly, allowing malicious prices to be swiftly uptaken”For several reasons, poke() is always callable as soon as block.timestamp / hop increments, regardless of when the last poke() call occurred (because zzz is rounded down to the nearest multiple of hop). This means the contract does not actually guarantee that a time interval of at least hop seconds has passed since the last poke() call before the next one; rather this is only (approximately) guaranteed if the last poke() call occurred shortly after the previous increase of block.timestamp / hop. Thus, a malicious price value can be acknowledged by the system in a time potentially much less than hop.
This was a deliberate design decision. The arguments that favoured it, roughly speaking, are:
- Providing a predictable time at which SKY holders should check for evidence of oracle attacks (in practice,
hopis 1 hour, so checks must be performed at the top of the hour) - Allowing all OSMs to be reliably poked at the same time in a single transaction
The fact that poke 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 poke() for a period of time. If anSKY holder observes that poke has not been promptly called, the actions they can take include:
- Call
poke()themselves and decide if the next value is malicious or not - Call
stop()orvoid()(the former if onlynxtis malicious; the latter if the malicious value is already incur) - 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 poke() calls in a short time window each hop period).
Authorization Attacks and Misconfigurations
Section titled “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
srcto either a malicious contract or to something that lacks apeek()interface, causing transactions thatpoke()the affected OSM to revert - Calling disruptive functions like
stopandvoidinappropriately
The only solution to these issues is diligence and care regarding the wards of the OSM.
Contract Details - Glossary (OSM)
Section titled “Contract Details - Glossary (OSM)”Storage Layout
Section titled “Storage Layout”stopped: flag (uint256) that disables price feed updates if non-zerosrc:addressof DSValue that the OSM will read fromONE_HOUR: 3600 seconds (uint16(3600))hop: time delay betweenpokecalls (uint16); defaults toONE_HOURzzz: time of last update (rounded down to nearest multiple ofhop)cur:Feedstruct that holds the current price valuenxt:Feedstruct that holds the next price valuebud: mapping fromaddresstouint256; whitelists feed readers
Public Methods
Section titled “Public Methods”Administrative Methods
Section titled “Administrative Methods”These functions can only be called by authorized addresses (i.e. addresses usr such that wards[usr] == 1).
rely/deny: add or remove authorized users (via modifications to thewardsmapping)stop()/start(): toggle whether price feed can be updated (by changing the value ofstopped)change(address): change data source for prices (by settingsrc)step(uint16): change interval between price updates (by settinghop)void(): similar tostop, except it also setscurandnxtto aFeedstruct with zero valueskiss(address)/diss(address): add/remove authorized feed consumers (via modifications to thebudsmapping)
Feed Reading Methods
Section titled “Feed Reading Methods”These can only be called by whitelisted addresses (i.e. addresses usr such that buds[usr] == 1):
peek(): returns the current feed value and a boolean indicating whether it is validpeep(): returns the next feed value (i.e. the one that will become the current value upon the nextpoke()call), and a boolean indicating whether it is validread(): returns the current feed value; reverts if it was not set by some valid mechanism
Feed Updating Methods
Section titled “Feed Updating Methods”poke(): updates the current feed value and reads the next one
Feed struct: a struct with two uint128 members, val and has. Used to store price feed data.
Released into the public domain (CC0 1.0 Universal) – trademarks remain with their owners; no warranty. See full license.