53 lines
1.8 KiB
Solidity
53 lines
1.8 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
|
|
/**
|
|
* @title WLPNAVOracle
|
|
* @notice Chainlink-style read interface for wLP / vault share price in USD (8 decimals) for lending adapters.
|
|
* @dev Keeper updates `latestAnswer` + `updatedAt`. Consumers must check `isStale()` before liquidations.
|
|
*/
|
|
contract WLPNAVOracle is AccessControl {
|
|
bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
|
|
|
|
/// @notice Price with 8 decimals (Chainlink USD convention).
|
|
int256 public latestAnswer;
|
|
uint256 public updatedAt;
|
|
uint256 public heartbeat;
|
|
bool public circuitBreaker;
|
|
|
|
event AnswerUpdated(int256 indexed answer, uint256 updatedAt, address indexed keeper);
|
|
event HeartbeatUpdated(uint256 heartbeat);
|
|
event CircuitBreakerSet(bool tripped);
|
|
|
|
constructor(address admin, uint256 heartbeat_) {
|
|
require(admin != address(0), "NAV: zero admin");
|
|
heartbeat = heartbeat_;
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
}
|
|
|
|
function setHeartbeat(uint256 h) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
heartbeat = h;
|
|
emit HeartbeatUpdated(h);
|
|
}
|
|
|
|
function setCircuitBreaker(bool on) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
circuitBreaker = on;
|
|
emit CircuitBreakerSet(on);
|
|
}
|
|
|
|
function submitAnswer(int256 answer) external onlyRole(KEEPER_ROLE) {
|
|
require(!circuitBreaker, "NAV: breaker");
|
|
latestAnswer = answer;
|
|
updatedAt = block.timestamp;
|
|
emit AnswerUpdated(answer, updatedAt, msg.sender);
|
|
}
|
|
|
|
function isStale() external view returns (bool) {
|
|
if (circuitBreaker) return true;
|
|
if (heartbeat == 0) return false;
|
|
return block.timestamp > updatedAt + heartbeat;
|
|
}
|
|
}
|