Hurdle Rate Oracle
Last updated
Last updated
The Hurdle Rate Oracle is a Solidity smart contract designed to fetch and store interest rates for multiple tokens in a gas-efficient manner. It leverages Chainlink Functions to retrieve external data and implements a bitmap-based storage mechanism that allows scaling up to 16 different token rates without increasing operational costs.
The primary focus of this contract is to track rates for liquid staking tokens like Lido and USDY, though it can be extended to support other tokens as well.
Github:
Fetches and stores rates for multiple tokens using Chainlink Functions
Uses a bitmap-based storage system for efficient data packing
Supports up to 16 different token rates in a single storage slot
Implements security features including reentrancy protection and rate update throttling
Includes admin controls for managing tokens and configuration
The contract is built on several core components:
Chainlink Functions Integration: Fetches external data through decentralized oracle networks
Bitmap Storage: Packs up to 16 different token rates into a single storage slot
Access Control: Implements ownership controls for administrative functions
Security Measures: Includes reentrancy protection and rate manipulation prevention
The contract uses an efficient bitmap-based storage system:
Each token rate occupies 16 bits (2 bytes) in the currentRates
storage slot
Rates are stored in basis points (10000 bps = 100%)
Up to 16 different token rates can be stored in a single storage slot
Each token is assigned a specific position (0-15) in the bitmap
An external caller triggers the updateRates()
function
The contract makes a Chainlink Functions request using the configured JavaScript source
The Chainlink DON (Decentralized Oracle Network) executes the JavaScript code
The callback function receives and stores the new rates
Rates can be accessed in several ways:
getRate(address token)
: Returns the rate for a specific token
getAllRates()
: Returns all rates as a packed bitmap
getRateByPosition(uint8 position)
: Returns the rate at a specific position
Chainlink Functions subscription
LINK tokens to fund the subscription
JavaScript source code for rate fetching
The contract is initialized with:
Chainlink Functions router address (Base Mainnet: 0xf9B8fc078197181C841c296C876945aaa425B278
)
Subscription ID (default: 36
)
JavaScript source code for rate fetching
By default, the contract registers:
WETH (Base) at position 0 - This will be used for Lido
USDCe (Base) at position 1 - This will be used for USDY
The contract can be deployed using the provided Foundry script:
By default, the contract is configured to fetch rates from the Maxapy API endpoint (https://api.maxapy.io/hurdle-rate
), which returns a pre-formatted bitmap of rates for the registered tokens.
Registers a new token at a specific position in the bitmap.
Updates the JavaScript source code for Chainlink Functions requests.
Updates the Chainlink Functions subscription ID.
Pauses or unpauses the contract.
Triggers a rate update via Chainlink Functions. This function can be called by anyone, but is protected by a minimum time interval between updates.
Returns the current rate for a specific token and the timestamp of the last update.
Returns all current rates as a packed bitmap.
Returns the rate at a specific position in the bitmap.
The contract implements several security features:
Reentrancy Protection: Uses the ReentrancyGuard to prevent reentrancy attacks
Rate Update Throttling: Enforces a minimum time interval between updates to prevent manipulation
Owner Controls: Administrative functions are restricted to the contract owner
Circuit Breaker: Includes a pause mechanism to stop operations in emergency situations
The JavaScript source code for Chainlink Functions must:
Return a packed bitmap of rates
Format each rate as a uint16 in basis points (0-10000)
Position each rate correctly in the bitmap
Handle errors gracefully
The contract comes with a default source code that fetches rates from the Maxapy API:
This source code:
Makes an HTTP request to the Maxapy API
Checks for errors in the response
Extracts the pre-formatted bitmap
Encodes it as a uint256 for the contract
To create a custom API integration, you'll need to ensure your API returns data in a format that can be converted to a bitmap. The expected format is:
Where bitmap
is a hexadecimal representation of a uint256 with packed rates. For example, the above bitmap represents:
Position 0: 500 bps (0x01F4)
Position 1: 500 bps (0x01F4)
All other positions: 0 bps
The contract emits the following events:
RateUpdateRequested(bytes32 indexed requestId)
: Emitted when a rate update is requested
RateUpdateFulfilled(bytes32 indexed requestId, uint256 newRates, uint256 timestamp)
: Emitted when rates are updated
TokenRegistered(address indexed token, uint8 position)
: Emitted when a new token is registered
RequestFailed(bytes32 indexed requestId, bytes error)
: Emitted when a Chainlink Functions request fails
SubscriptionIdUpdated(uint64 oldSubId, uint64 newSubId)
: Emitted when the subscription ID is updated
SourceUpdated(string newSource)
: Emitted when the source code is updated
PauseStateChanged(bool paused)
: Emitted when the pause state is changed
The contract uses custom errors to provide clear failure messages:
InvalidPosition
: Position is out of range (must be 0-15)
RequestNotFound
: Request ID doesn't match any pending request
TokenNotRegistered
: Attempted to get rate for unregistered token
TokenAlreadyRegistered
: Attempted to register token at occupied position
UpdateTooFrequent
: Rate update requested too soon after previous update
InvalidRates
: Returned rates are invalid
InvalidSubscription
: Subscription ID is invalid
RateLimit
: Too many rates provided
InvalidSubscriptionId
: Invalid subscription ID provided
Paused
: Contract is paused
AddressZero
: Zero address provided
InvalidSource
: Empty source code provided
You can trigger rate updates using a simple Node.js script:
This script requires an .env
file with:
RPC_URL
: Base network RPC URL
PRIVATE_KEY
: Private key of the wallet that will pay for the transaction
The use of bitmap storage provides significant gas savings:
Reading rates costs minimal gas due to efficient packing
Updating all rates costs the same as updating a single rate
No iteration required to access specific rates
Maximum of 16 token rates per contract instance
Rate precision limited to basis points (0.01%)
Rate updates are limited by the MIN_UPDATE_INTERVAL (1 hour)
Dependent on Chainlink Functions subscription being funded with LINK
MAX_RATE_BPS
: Maximum allowed rate in basis points (10000 = 100%)
CALLBACK_GAS_LIMIT
: Gas limit for Chainlink Functions callback (300,000)
MIN_UPDATE_INTERVAL
: Minimum time between rate updates (1 hour)
1 basis point = 0.01%
100 basis points = 1%
10000 basis points = 100%