Skip to content

Commit

Permalink
change filters for updating token rate
Browse files Browse the repository at this point in the history
  • Loading branch information
kovalgek committed Apr 21, 2024
1 parent 1b8cd59 commit 0e222ec
Showing 1 changed file with 96 additions and 37 deletions.
133 changes: 96 additions & 37 deletions contracts/optimism/TokenRateOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ pragma solidity 0.8.10;
import {ITokenRateUpdatable} from "./interfaces/ITokenRateUpdatable.sol";
import {IChainlinkAggregatorInterface} from "./interfaces/IChainlinkAggregatorInterface.sol";
import {CrossDomainEnabled} from "./CrossDomainEnabled.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Versioned} from "../utils/Versioned.sol";

interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {}

/// @author kovalgek
/// @notice Oracle for storing token rate.
contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle {
/// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge.
contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Initializable {

/// @dev Stores the dynamic data of the oracle. Allows safely use of this
/// contract with upgradable proxies
Expand All @@ -35,14 +38,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle {
/// @notice A time period when token rate can be considered outdated.
uint256 public immutable TOKEN_RATE_OUTDATED_DELAY;

/// @notice A time period when token rate can be considered outdated.
uint256 public constant MAX_ALLOWED_L1_L2_TIME_DIFFERENCE = 86400;

/// @notice Number of seconds in one day.
uint256 public constant ONE_DAY_SECONDS = 86400;

/// @notice Allowed token rate deviation per day in basic points.
uint256 public constant TOKEN_RATE_DEVIATION = 500;

/// @notice Decimals of the oracle response.
uint8 public constant DECIMALS = 18;

/// @notice The minimum value that the token rate can be
uint256 public constant MIN_TOKEN_RATE = 1_000_000_000_000_000; // 0.001

/// @notice The maximum value that the token rate can be.
uint256 public constant MAX_TOKEN_RATE = 1_000_000_000_000_000_000_000; // 1000
/// @notice Basic point scale.
uint256 private constant BASIS_POINT_SCALE = 1e4;

/// @param messenger_ L2 messenger address being used for cross-chain communications
/// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle.
Expand All @@ -57,6 +66,13 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle {
L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_;
L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_;
TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_;

_disableInitializers();
}

function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external initializer {
_initializeContractVersionTo(1);
_updateRate(tokenRate_, rateL1Timestamp_);
}

/// @inheritdoc IChainlinkAggregatorInterface
Expand All @@ -67,20 +83,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle {
uint256 updatedAt_,
uint80 answeredInRound_
) {
uint80 roundId = uint80(_loadTokenRateData().rateL1Timestamp);
uint80 roundId = uint80(_rateL1Timestamp());

return (
roundId,
int256(uint(_loadTokenRateData().tokenRate)),
_loadTokenRateData().rateL1Timestamp,
_loadTokenRateData().rateL1Timestamp,
int256(uint256(_tokenRate())),
_rateL1Timestamp(),
_rateL1Timestamp(),
roundId
);
}

/// @inheritdoc IChainlinkAggregatorInterface
function latestAnswer() external view returns (int256) {
return int256(uint(_loadTokenRateData().tokenRate));
return int256(uint256(_tokenRate()));
}

/// @inheritdoc IChainlinkAggregatorInterface
Expand All @@ -89,45 +105,41 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle {
}

/// @inheritdoc ITokenRateUpdatable
function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external {
function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyAuthorized {
_updateRate(tokenRate_, rateL1Timestamp_);
}

if (!_isAuthorized(msg.sender)) {
revert ErrorNoRights(msg.sender);
}
/// @notice Returns flag that shows that token rate can be considered outdated.
function isLikelyOutdated() external view returns (bool) {
return block.timestamp - _rateL1Timestamp() > TOKEN_RATE_OUTDATED_DELAY;
}

if (rateL1Timestamp_ < _loadTokenRateData().rateL1Timestamp) {
emit NewTokenRateOutdated(tokenRate_, _loadTokenRateData().rateL1Timestamp, rateL1Timestamp_);
function _updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) internal {
if (rateL1Timestamp_ < _rateL1Timestamp()) {
emit ATryToUpdateTokenRateWithOutdatedTime(tokenRate_, _rateL1Timestamp(), rateL1Timestamp_);
return;
}

if (rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L1_L2_TIME_DIFFERENCE) {
revert ErrorInvalidTime(tokenRate_, rateL1Timestamp_);
}

if (rateL1Timestamp_ > block.timestamp) {
revert ErrorL1TimestampInFuture(tokenRate_, rateL1Timestamp_);
emit TokenRateL1TimestampAheadOfL2Time();
}

if (tokenRate_ < MIN_TOKEN_RATE || tokenRate_ > MAX_TOKEN_RATE) {
if (!_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) {
revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_);
}

if (tokenRate_ == _loadTokenRateData().tokenRate && rateL1Timestamp_ == _loadTokenRateData().rateL1Timestamp) {
if (tokenRate_ == _tokenRate() && rateL1Timestamp_ == _rateL1Timestamp()) {
return;
}

_loadTokenRateData().tokenRate = uint192(tokenRate_);
_loadTokenRateData().rateL1Timestamp = uint64(rateL1Timestamp_);

emit RateUpdated(_loadTokenRateData().tokenRate, _loadTokenRateData().rateL1Timestamp);
}

/// @notice Returns flag that shows that token rate can be considered outdated.
function isLikelyOutdated() external view returns (bool) {
return block.timestamp - _loadTokenRateData().rateL1Timestamp > TOKEN_RATE_OUTDATED_DELAY;
}
_setTokenRate(uint192(tokenRate_));
_setRateL1Timestamp(uint64(rateL1Timestamp_));

function _isAuthorized(address caller_) internal view returns (bool) {
bool isCalledFromL1TokenRatePusher = caller_ == address(MESSENGER) &&
MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER;
bool isCalledFromERC20TokenRateBridge = caller_ == L2_ERC20_TOKEN_BRIDGE;
return isCalledFromL1TokenRatePusher || isCalledFromERC20TokenRateBridge;
emit RateUpdated(_tokenRate(), _rateL1Timestamp());
}

/// @dev Returns the reference to the slot with TokenRateData struct
Expand All @@ -142,10 +154,57 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle {
}
}

function _isTokenRateWithinAllowedRange(uint256 tokenRate_, uint256 rateL1Timestamp_) internal view returns (bool) {
uint256 rateL1TimestampDiff = rateL1Timestamp_ - _rateL1Timestamp();
uint256 roundedUpNumberOfDays = rateL1TimestampDiff / ONE_DAY_SECONDS + 1;
uint256 allowedTokenRateDeviation = roundedUpNumberOfDays * TOKEN_RATE_DEVIATION;
uint256 topTokenRateLimitFactor = 1 + allowedTokenRateDeviation / BASIS_POINT_SCALE;
uint256 bottomTokenRateLimitFactor =
allowedTokenRateDeviation <= BASIS_POINT_SCALE ?
(1 - allowedTokenRateDeviation / BASIS_POINT_SCALE) : 0;

return tokenRate_ <= _tokenRate() * topTokenRateLimitFactor &&
tokenRate_ >= _tokenRate() * bottomTokenRateLimitFactor;
}

function _isAuthorized(address caller_) internal view returns (bool) {
if(caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) {
return true;
}
if(caller_ == L2_ERC20_TOKEN_BRIDGE) {
return true;
}
return false;
}

function _tokenRate() private view returns (uint192) {
return _loadTokenRateData().tokenRate;
}

function _rateL1Timestamp() private view returns (uint64) {
return _loadTokenRateData().rateL1Timestamp;
}

function _setTokenRate(uint192 tokenRate_) internal {
_loadTokenRateData().tokenRate = tokenRate_;
}

function _setRateL1Timestamp(uint64 rateL1Timestamp_) internal {
_loadTokenRateData().rateL1Timestamp = rateL1Timestamp_;
}

modifier onlyAuthorized() {
if(!_isAuthorized(msg.sender)) {
revert ErrorNoRights(msg.sender);
}
_;
}

event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_);
event NewTokenRateOutdated(uint256 tokenRate_, uint256 rateL1Timestamp_, uint256 newTateL1Timestamp_);
event ATryToUpdateTokenRateWithOutdatedTime(uint256 tokenRate_, uint256 rateL1Timestamp_, uint256 newTateL1Timestamp_);
event TokenRateL1TimestampAheadOfL2Time();

error ErrorNoRights(address caller);
error ErrorL1TimestampInFuture(uint256 tokenRate_, uint256 rateL1Timestamp_);
error ErrorInvalidTime(uint256 tokenRate_, uint256 rateL1Timestamp_);
error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_);
}

0 comments on commit 0e222ec

Please sign in to comment.