Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] Improvement of Price Oracle Sentinel #688

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions contracts/dependencies/chainlink/AggregatorV3Interface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
// Chainlink Contracts v0.8
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
function decimals() external view returns (uint8);

function description() external view returns (string memory);

function version() external view returns (uint256);

// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);

function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
29 changes: 27 additions & 2 deletions contracts/interfaces/IPriceOracleSentinel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ interface IPriceOracleSentinel {
*/
event GracePeriodUpdated(uint256 newGracePeriod);

/**
* @dev Emitted after the price expiration time is updated
* @param newPriceExpirationTime The new price expiration time
*/
event PriceExpirationTimeUpdated(uint256 newPriceExpirationTime);

/**
* @notice Returns the PoolAddressesProvider
* @return The address of the PoolAddressesProvider contract
Expand All @@ -30,16 +36,23 @@ interface IPriceOracleSentinel {
/**
* @notice Returns true if the `borrow` operation is allowed.
* @dev Operation not allowed when PriceOracle is down or grace period not passed.
* @param priceOracle The address of the price oracle
* @param asset The address of the asset to borrow
* @return True if the `borrow` operation is allowed, false otherwise.
*/
function isBorrowAllowed() external view returns (bool);
function isBorrowAllowed(address priceOracle, address asset) external view returns (bool);

/**
* @notice Returns true if the `liquidation` operation is allowed.
* @dev Operation not allowed when PriceOracle is down or grace period not passed.
* @param priceOracle The address of the price oracle
* @param debtAsset The address of the debt asset to liquidate
* @return True if the `liquidation` operation is allowed, false otherwise.
*/
function isLiquidationAllowed() external view returns (bool);
function isLiquidationAllowed(address priceOracle, address debtAsset)
external
view
returns (bool);

/**
* @notice Updates the address of the sequencer oracle
Expand All @@ -53,6 +66,12 @@ interface IPriceOracleSentinel {
*/
function setGracePeriod(uint256 newGracePeriod) external;

/**
* @notice Updates the price expiration time
* @param newPriceExpirationTime The value of the new price expiration time
*/
function setPriceExpirationTime(uint256 newPriceExpirationTime) external;

/**
* @notice Returns the SequencerOracle
* @return The address of the sequencer oracle contract
Expand All @@ -64,4 +83,10 @@ interface IPriceOracleSentinel {
* @return The duration of the grace period
*/
function getGracePeriod() external view returns (uint256);

/**
* @notice Returns the price expiration time
* @return The duration after the price of assets can be considered as expired or stale (in seconds)
*/
function getPriceExpirationTime() external view returns (uint256);
}
28 changes: 28 additions & 0 deletions contracts/mocks/oracle/CLAggregators/MockAggregator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ pragma solidity 0.8.10;

contract MockAggregator {
int256 private _latestAnswer;
uint80 private _roundId;
uint256 private _startedAt;
uint256 private _updatedAt;

event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);

constructor(int256 initialAnswer) {
_latestAnswer = initialAnswer;
_startedAt = block.timestamp;
emit AnswerUpdated(initialAnswer, 0, block.timestamp);
}

Expand All @@ -22,4 +26,28 @@ contract MockAggregator {
function decimals() external pure returns (uint8) {
return 8;
}

function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (
_roundId,
_latestAnswer,
_startedAt,
_updatedAt == 0 ? block.timestamp : _updatedAt,
_roundId
);
}

function setLastUpdateTimestamp(uint256 updatedAt) external {
_updatedAt = updatedAt;
}
}
46 changes: 41 additions & 5 deletions contracts/protocol/configuration/PriceOracleSentinel.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.10;

import {AggregatorV3Interface} from '../../dependencies/chainlink/AggregatorV3Interface.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {IPoolAddressesProvider} from '../../interfaces/IPoolAddressesProvider.sol';
import {IPriceOracleSentinel} from '../../interfaces/IPriceOracleSentinel.sol';
import {ISequencerOracle} from '../../interfaces/ISequencerOracle.sol';
import {IACLManager} from '../../interfaces/IACLManager.sol';
import {IAaveOracle} from '../../interfaces/IAaveOracle.sol';

/**
* @title PriceOracleSentinel
Expand Down Expand Up @@ -42,30 +44,40 @@ contract PriceOracleSentinel is IPriceOracleSentinel {

uint256 internal _gracePeriod;

uint256 internal _priceExpirationTime;

/**
* @dev Constructor
* @param provider The address of the PoolAddressesProvider
* @param oracle The address of the SequencerOracle
* @param gracePeriod The duration of the grace period in seconds
* @param priceExpirationTime The expiration time of asset prices in seconds
*/
constructor(
IPoolAddressesProvider provider,
ISequencerOracle oracle,
uint256 gracePeriod
uint256 gracePeriod,
uint256 priceExpirationTime
) {
ADDRESSES_PROVIDER = provider;
_sequencerOracle = oracle;
_gracePeriod = gracePeriod;
_priceExpirationTime = priceExpirationTime;
}

/// @inheritdoc IPriceOracleSentinel
function isBorrowAllowed() public view override returns (bool) {
return _isUpAndGracePeriodPassed();
function isBorrowAllowed(address priceOracle, address asset) public view override returns (bool) {
return _isUpAndGracePeriodPassed() && !_isPriceStale(priceOracle, asset);
}

/// @inheritdoc IPriceOracleSentinel
function isLiquidationAllowed() public view override returns (bool) {
return _isUpAndGracePeriodPassed();
function isLiquidationAllowed(address priceOracle, address debtAsset)
public
view
override
returns (bool)
{
return _isUpAndGracePeriodPassed() && !_isPriceStale(priceOracle, debtAsset);
}

/**
Expand All @@ -77,6 +89,19 @@ contract PriceOracleSentinel is IPriceOracleSentinel {
return answer == 0 && block.timestamp - lastUpdateTimestamp > _gracePeriod;
}

/**
* @notice Checks the price of the asset is not stale. It can be considered stale if the time passed since the last
* is longer than the price expiration time.
* @param priceOracle The address of the price oracle
* @param asset The address of the asset to check its price status
* @return True if the price is stale, false otherwise
*/
function _isPriceStale(address priceOracle, address asset) internal view returns (bool) {
address source = IAaveOracle(priceOracle).getSourceOfAsset(asset);
(, , , uint256 updatedAt, ) = AggregatorV3Interface(source).latestRoundData();
return (block.timestamp - updatedAt) > _priceExpirationTime;
}

/// @inheritdoc IPriceOracleSentinel
function setSequencerOracle(address newSequencerOracle) public onlyPoolAdmin {
_sequencerOracle = ISequencerOracle(newSequencerOracle);
Expand All @@ -89,6 +114,12 @@ contract PriceOracleSentinel is IPriceOracleSentinel {
emit GracePeriodUpdated(newGracePeriod);
}

/// @inheritdoc IPriceOracleSentinel
function setPriceExpirationTime(uint256 newPriceExpirationTime) public onlyRiskOrPoolAdmins {
_priceExpirationTime = newPriceExpirationTime;
emit PriceExpirationTimeUpdated(newPriceExpirationTime);
}

/// @inheritdoc IPriceOracleSentinel
function getSequencerOracle() public view returns (address) {
return address(_sequencerOracle);
Expand All @@ -98,4 +129,9 @@ contract PriceOracleSentinel is IPriceOracleSentinel {
function getGracePeriod() public view returns (uint256) {
return _gracePeriod;
}

/// @inheritdoc IPriceOracleSentinel
function getPriceExpirationTime() public view returns (uint256) {
return _priceExpirationTime;
}
}
4 changes: 3 additions & 1 deletion contracts/protocol/libraries/logic/LiquidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@ library LiquidationLogic {
collateralReserve,
DataTypes.ValidateLiquidationCallParams({
debtReserveCache: vars.debtReserveCache,
debtAsset: params.debtAsset,
totalDebt: vars.userTotalDebt,
healthFactor: vars.healthFactor,
priceOracleSentinel: params.priceOracleSentinel
priceOracleSentinel: params.priceOracleSentinel,
oracle: params.priceOracle
})
);

Expand Down
10 changes: 8 additions & 2 deletions contracts/protocol/libraries/logic/ValidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ library ValidationLogic {

require(
params.priceOracleSentinel == address(0) ||
IPriceOracleSentinel(params.priceOracleSentinel).isBorrowAllowed(),
IPriceOracleSentinel(params.priceOracleSentinel).isBorrowAllowed(
params.oracle,
params.asset
),
Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED
);

Expand Down Expand Up @@ -521,7 +524,10 @@ library ValidationLogic {
require(
params.priceOracleSentinel == address(0) ||
params.healthFactor < MINIMUM_HEALTH_FACTOR_LIQUIDATION_THRESHOLD ||
IPriceOracleSentinel(params.priceOracleSentinel).isLiquidationAllowed(),
IPriceOracleSentinel(params.priceOracleSentinel).isLiquidationAllowed(
params.oracle,
params.debtAsset
),
Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED
);

Expand Down
2 changes: 2 additions & 0 deletions contracts/protocol/libraries/types/DataTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,11 @@ library DataTypes {

struct ValidateLiquidationCallParams {
ReserveCache debtReserveCache;
address debtAsset;
uint256 totalDebt;
uint256 healthFactor;
address priceOracleSentinel;
address oracle;
}

struct CalculateInterestRatesParams {
Expand Down
Loading