// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AutomationCompatibleInterface {
/**
* @notice method that is simulated by the keepers to see if any work actually
* needs to be performed. This method does does not actually need to be
* executable, and since it is only ever simulated it can consume lots of gas.
* @dev To ensure that it is never called, you may want to add the
* cannotExecute modifier from KeeperBase to your implementation of this
* method.
* @param checkData specified in the upkeep registration so it is always the
* same for a registered upkeep. This can easily be broken down into specific
* arguments using `abi.decode`, so multiple upkeeps can be registered on the
* same contract and easily differentiated by the contract.
* @return upkeepNeeded boolean to indicate whether the keeper should call
* performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try
* `abi.encode`.
*/
function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData);
/**
* @notice method that is actually executed by the keepers, via the registry.
* The data returned by the checkUpkeep simulation will be passed into
* this method to actually be executed.
* @dev The input to this method should not be trusted, and the caller of the
* method should not even be restricted to any single registry. Anyone should
* be able call it, and the input should be validated, there is no guarantee
* that the data passed in is the performData returned from checkUpkeep. This
* could happen due to malicious keepers, racing keepers, or simply a state
* change while the performUpkeep transaction is waiting for confirmation.
* Always validate the data passed in.
* @param performData is the data which was passed back from the checkData
* simulation. If it is encoded, it can easily be decoded into other types by
* calling `abi.decode`. This data should not be trusted, and should be
* validated against the contract's current state.
*/
function performUpkeep(bytes calldata performData) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
import { ERC4626 } from "@solmate/mixins/ERC4626.sol";
import { Math } from "src/utils/Math.sol";
import { Owned } from "@solmate/auth/Owned.sol";
import { AutomationCompatibleInterface } from "@chainlink/contracts/src/v0.8/interfaces/AutomationCompatibleInterface.sol";
contract ERC4626SharePriceOracle is AutomationCompatibleInterface {
using Math for uint256;
// ========================================= STRUCTS =========================================
struct Observation {
uint64 timestamp;
uint192 cumulative;
}
// ========================================= GLOBAL STATE =========================================
/**
* @notice The latest stored onchain answer.
*/
uint216 public answer;
/**
* @notice Stores the index of observations with the pending Observation.
*/
uint16 public currentIndex;
/**
* @notice The length of the observations array.
* @dev `observations` will never change its length once set in the constructor.
* By saving this value here, we can take advantage of variable packing to make reads cheaper.
* @dev This is not immutable to make it easier in the future to create oracles that can expand their observations.
*/
uint16 public observationsLength;
/**
* @notice Triggered when answer provided by Chainlink Automation is extreme.
* @dev true: No further upkeeps are allowed, `getLatest` and `getLatestAnswer` will return true error bools.
* false: Continue as normal.
*/
bool public killSwitch;
/**
* @notice Stores the observations this contract uses to derive a
* time weighted average answer.
*/
Observation[] public observations;
/**
* @notice Decimals used to scale share price for internal calculations.
*/
uint8 public constant decimals = 18;
//============================== ERRORS ===============================
error ERC4626SharePriceOracle__OnlyCallableByAutomationRegistry();
error ERC4626SharePriceOracle__StalePerformData();
error ERC4626SharePriceOracle__CumulativeTooLarge();
error ERC4626SharePriceOracle__NoUpkeepConditionMet();
error ERC4626SharePriceOracle__SharePriceTooLarge();
error ERC4626SharePriceOracle__FuturePerformData();
error ERC4626SharePriceOracle__ContractKillSwitch();
//============================== EVENTS ===============================
/**
* @notice Emitted when performUpkeep is ran.
* @param timeUpdated the time the answer was updated on chain
* @param timeAnswerCalculated the time the answer was calculated in checkUpkeep
* @param latestAnswer the new answer
* @param timeWeightedAverageAnswer the new time weighted average answer
* @param isNotSafeToUse bool
* if true: `timeWeightedAverageAnswer` is illogical, use `latestAnswer`
* if false: use `timeWeightedAverageAnswer`
*/
event OracleUpdated(
uint256 timeUpdated,
uint256 timeAnswerCalculated,
uint256 latestAnswer,
uint256 timeWeightedAverageAnswer,
bool isNotSafeToUse
);
event KillSwitchActivated(uint256 reportedAnswer, uint256 minAnswer, uint256 maxAnswer);
//============================== IMMUTABLES ===============================
/**
* @notice Determines the minimum time for each observation, and is used to determine if an
* answer is stale.
*/
uint64 public immutable heartbeat;
/**
* @notice Used to enforce that the summation of each observations delay used in
* a time weighed average calculation is less than the gracePeriod.
* @dev Example: Using a 3 day TWAA with 1 hour grace period.
* When calculating the TWAA, the total time delta for completed observations must be greater than 3 days but less than
* 3 days + 1hr. So one observation could be delayed 1 hr, or two observations could be
* delayed 30 min each.
*/
uint64 public immutable gracePeriod;
/**
* @notice Number between 0 -> 10_000 that determines how far off the last saved answer
* can deviate from the current answer.
* @dev This value should be reflective of the vaults expected maximum percent share
* price change during a heartbeat duration.
* @dev
* -1_000 == 10%
* -100 == 1%
* -10 == 0.1%
* -1 == 0.01% or 1 bps
*/
uint64 public immutable deviationTrigger;
/**
* @notice One share of target vault.
*/
uint256 public immutable ONE_SHARE;
/**
* @notice Chainlink's Automation Registry contract address.
* @notice For mainnet use 0x02777053d6764996e594c3E88AF1D58D5363a2e6.
*/
address public immutable automationRegistry;
/**
* @notice ERC4626 target vault this contract is an oracle for.
*/
ERC4626 public immutable target;
/**
* @notice Target vault decimals.
*/
uint8 public immutable targetDecimals;
/**
* @notice Multiplier with 4 decimals that determines the acceptable lower band
* for a performUpkeep answer.
*/
uint256 public immutable allowedAnswerChangeLower;
/**
* @notice Multiplier with 4 decimals that determines the acceptable upper band
* for a performUpkeep answer.
*/
uint256 public immutable allowedAnswerChangeUpper;
/**
* @notice TWAA Minimum Duration = `_observationsToUse` * `_heartbeat`.
* @notice TWAA Maximum Duration = `_observationsToUse` * `_heartbeat` + `gracePeriod` + `_heartbeat`.
* @notice TWAA calculations will use the current pending observation, and then `_observationsToUse` observations.
*/
constructor(
ERC4626 _target,
uint64 _heartbeat,
uint64 _deviationTrigger,
uint64 _gracePeriod,
uint16 _observationsToUse,
address _automationRegistry,
uint216 _startingAnswer,
uint256 _allowedAnswerChangeLower,
uint256 _allowedAnswerChangeUpper
) {
target = _target;
targetDecimals = target.decimals();
ONE_SHARE = 10 ** targetDecimals;
heartbeat = _heartbeat;
deviationTrigger = _deviationTrigger;
gracePeriod = _gracePeriod;
automationRegistry = _automationRegistry;
// Add 1 to observations to use.
_observationsToUse = _observationsToUse + 1;
observationsLength = _observationsToUse;
// Grow Observations array to required length, and fill it with observations that use 1 for timestamp and cumulative.
// That way the initial upkeeps won't need to change state from 0 which is more expensive.
for (uint256 i; i < _observationsToUse; ++i) observations.push(Observation({ timestamp: 1, cumulative: 1 }));
// Set to _startingAnswer so slot is dirty for first upkeep, and does not trigger kill switch.
answer = _startingAnswer;
if (_allowedAnswerChangeLower > 1e4) revert("Illogical Lower");
allowedAnswerChangeLower = _allowedAnswerChangeLower;
if (_allowedAnswerChangeUpper < 1e4) revert("Illogical Upper");
allowedAnswerChangeUpper = _allowedAnswerChangeUpper;
}
//============================== CHAINLINK AUTOMATION ===============================
/**
* @notice Leverages Automation V2 secure offchain computation to run expensive share price calculations offchain,
* then inject them onchain using `performUpkeep`.
*/
function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory performData) {
// Get target share price.
uint216 sharePrice = _getTargetSharePrice();
// Read state from one slot.
uint256 _answer = answer;
uint16 _currentIndex = currentIndex;
uint16 _observationsLength = observationsLength;
bool _killSwitch = killSwitch;
if (!_killSwitch) {
// See if we need to update because answer is stale or outside deviation.
// Time since answer was last updated.
uint256 timeDeltaCurrentAnswer = block.timestamp - observations[_currentIndex].timestamp;
uint256 timeDeltaSincePreviousObservation = block.timestamp -
observations[_getPreviousIndex(_currentIndex, _observationsLength)].timestamp;
uint64 _heartbeat = heartbeat;
if (
timeDeltaCurrentAnswer >= _heartbeat ||
timeDeltaSincePreviousObservation >= _heartbeat ||
sharePrice > _answer.mulDivDown(1e4 + deviationTrigger, 1e4) ||
sharePrice < _answer.mulDivDown(1e4 - deviationTrigger, 1e4)
) {
// We need to update answer.
upkeepNeeded = true;
performData = abi.encode(sharePrice, uint64(block.timestamp));
}
} // else no upkeep is needed
}
/**
* @notice Save answer on chain, and update observations if needed.
*/
function performUpkeep(bytes calldata performData) external {
if (msg.sender != automationRegistry) revert ERC4626SharePriceOracle__OnlyCallableByAutomationRegistry();
(uint216 sharePrice, uint64 currentTime) = abi.decode(performData, (uint216, uint64));
// Verify atleast one of the upkeep conditions was met.
bool upkeepConditionMet;
// Read state from one slot.
uint256 _answer = answer;
uint16 _currentIndex = currentIndex;
uint16 _observationsLength = observationsLength;
bool _killSwitch = killSwitch;
if (_killSwitch) revert ERC4626SharePriceOracle__ContractKillSwitch();
// See if kill switch should be activated based on change between answers.
if (_checkIfKillSwitchShouldBeTriggered(sharePrice, _answer)) return;
// See if we are upkeeping because of deviation.
if (
sharePrice > uint256(_answer).mulDivDown(1e4 + deviationTrigger, 1e4) ||
sharePrice < uint256(_answer).mulDivDown(1e4 - deviationTrigger, 1e4)
) upkeepConditionMet = true;
// Update answer.
answer = sharePrice;
// Update current observation.
Observation storage currentObservation = observations[_currentIndex];
// Make sure time is larger than previous time.
if (currentTime <= currentObservation.timestamp) revert ERC4626SharePriceOracle__StalePerformData();
// Make sure time is not in the future.
if (currentTime > block.timestamp) revert ERC4626SharePriceOracle__FuturePerformData();
// See if we are updating because of staleness.
uint256 timeDelta = currentTime - currentObservation.timestamp;
if (timeDelta >= heartbeat) upkeepConditionMet = true;
// Use the old answer to calculate cumulative.
uint256 currentCumulative = currentObservation.cumulative + (_answer * timeDelta);
if (currentCumulative > type(uint192).max) revert ERC4626SharePriceOracle__CumulativeTooLarge();
currentObservation.cumulative = uint192(currentCumulative);
currentObservation.timestamp = currentTime;
uint256 timeDeltaSincePreviousObservation = currentTime -
observations[_getPreviousIndex(_currentIndex, _observationsLength)].timestamp;
// See if we need to advance to the next cumulative.
if (timeDeltaSincePreviousObservation >= heartbeat) {
uint16 nextIndex = _getNextIndex(_currentIndex, _observationsLength);
currentIndex = nextIndex;
// Update memory variable for event.
_currentIndex = nextIndex;
// Update newest cumulative.
Observation storage newObservation = observations[nextIndex];
newObservation.cumulative = uint192(currentCumulative);
newObservation.timestamp = currentTime;
upkeepConditionMet = true;
}
if (!upkeepConditionMet) revert ERC4626SharePriceOracle__NoUpkeepConditionMet();
(uint256 timeWeightedAverageAnswer, bool isNotSafeToUse) = _getTimeWeightedAverageAnswer(
sharePrice,
_currentIndex,
_observationsLength
);
// See if kill switch should be activated based on change between proposed answer and time weighted average answer.
if (!isNotSafeToUse && _checkIfKillSwitchShouldBeTriggered(sharePrice, timeWeightedAverageAnswer)) return;
emit OracleUpdated(block.timestamp, currentTime, sharePrice, timeWeightedAverageAnswer, isNotSafeToUse);
}
//============================== ORACLE VIEW FUNCTIONS ===============================
/**
* @notice Get the latest answer, time weighted average answer, and bool indicating whether they can be safely used.
*/
function getLatest() external view returns (uint256 ans, uint256 timeWeightedAverageAnswer, bool notSafeToUse) {
// Read state from one slot.
ans = answer;
uint16 _currentIndex = currentIndex;
uint16 _observationsLength = observationsLength;
bool _killSwitch = killSwitch;
if (_killSwitch) return (0, 0, true);
// Check if answer is stale, if so set notSafeToUse to true, and return.
uint256 timeDeltaSinceLastUpdated = block.timestamp - observations[currentIndex].timestamp;
// Note add in the grace period here, because it can take time for the upkeep TX to go through.
if (timeDeltaSinceLastUpdated > (heartbeat + gracePeriod)) return (0, 0, true);
(timeWeightedAverageAnswer, notSafeToUse) = _getTimeWeightedAverageAnswer(
ans,
_currentIndex,
_observationsLength
);
if (notSafeToUse) return (0, 0, true);
}
/**
* @notice Get the latest answer, and bool indicating whether answer is safe to use or not.
*/
function getLatestAnswer() external view returns (uint256, bool) {
uint256 _answer = answer;
bool _killSwitch = killSwitch;
if (_killSwitch) return (0, true);
// Check if answer is stale, if so set notSafeToUse to true, and return.
uint256 timeDeltaSinceLastUpdated = block.timestamp - observations[currentIndex].timestamp;
// Note add in the grace period here, because it can take time for the upkeep TX to go through.
if (timeDeltaSinceLastUpdated > (heartbeat + gracePeriod)) return (0, true);
return (_answer, false);
}
//============================== INTERNAL HELPER FUNCTIONS ===============================
/**
* @notice Get the next index of observations array.
*/
function _getNextIndex(uint16 _currentIndex, uint16 _length) internal pure returns (uint16 nextIndex) {
nextIndex = (_currentIndex == _length - 1) ? 0 : _currentIndex + 1;
}
/**
* @notice Get the previous index of observations array.
*/
function _getPreviousIndex(uint16 _currentIndex, uint16 _length) internal pure returns (uint16 previousIndex) {
previousIndex = (_currentIndex == 0) ? _length - 1 : _currentIndex - 1;
}
/**
* @notice Use observations to get the time weighted average answer.
*/
function _getTimeWeightedAverageAnswer(
uint256 _answer,
uint16 _currentIndex,
uint16 _observationsLength
) internal view returns (uint256 timeWeightedAverageAnswer, bool notSafeToUse) {
// Read observations from storage.
Observation memory mostRecentlyCompletedObservation = observations[
_getPreviousIndex(_currentIndex, _observationsLength)
];
Observation memory oldestObservation = observations[_getNextIndex(_currentIndex, _observationsLength)];
// Data is not set.
if (oldestObservation.timestamp == 1) return (0, true);
// Make sure that the old observations we are using are not too stale.
uint256 timeDelta = mostRecentlyCompletedObservation.timestamp - oldestObservation.timestamp;
/// @dev use _length - 2 because
/// remove 1 because observations array stores the current pending observation.
/// remove 1 because we are really interested in the time between observations.
uint256 minDuration = heartbeat * (_observationsLength - 2);
uint256 maxDuration = minDuration + gracePeriod;
// Data is too new
if (timeDelta < minDuration) return (0, true);
// Data is too old
if (timeDelta > maxDuration) return (0, true);
Observation memory latestObservation = observations[_currentIndex];
uint192 latestCumulative = latestObservation.cumulative +
uint192((_answer * (block.timestamp - latestObservation.timestamp)));
timeWeightedAverageAnswer =
(latestCumulative - oldestObservation.cumulative) /
(block.timestamp - oldestObservation.timestamp);
}
/**
* @notice Get the target ERC4626's share price using totalAssets, and totalSupply.
*/
function _getTargetSharePrice() internal view returns (uint216 sharePrice) {
uint256 totalShares = target.totalSupply();
// Get total Assets but scale it up to decimals decimals of precision.
uint256 totalAssets = target.totalAssets().changeDecimals(targetDecimals, decimals);
if (totalShares == 0) return 0;
uint256 _sharePrice = ONE_SHARE.mulDivDown(totalAssets, totalShares);
if (_sharePrice > type(uint216).max) revert ERC4626SharePriceOracle__SharePriceTooLarge();
sharePrice = uint216(_sharePrice);
}
/**
* @notice Activate the kill switch if `proposedAnswer` is extreme when compared to `answerToCompareAgainst`
* @return bool indicating whether calling function should immediately exit or not.
*/
function _checkIfKillSwitchShouldBeTriggered(
uint256 proposedAnswer,
uint256 answerToCompareAgainst
) internal returns (bool) {
if (
proposedAnswer < answerToCompareAgainst.mulDivDown(allowedAnswerChangeLower, 1e4) ||
proposedAnswer > answerToCompareAgainst.mulDivDown(allowedAnswerChangeUpper, 1e4)
) {
killSwitch = true;
emit KillSwitchActivated(
proposedAnswer,
answerToCompareAgainst.mulDivDown(allowedAnswerChangeLower, 1e4),
answerToCompareAgainst.mulDivDown(allowedAnswerChangeUpper, 1e4)
);
return true;
}
return false;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
library Math {
/**
* @notice Substract with a floor of 0 for the result.
*/
function subMinZero(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? x - y : 0;
}
/**
* @notice Used to change the decimals of precision used for an amount.
*/
function changeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) {
if (fromDecimals == toDecimals) {
return amount;
} else if (fromDecimals < toDecimals) {
return amount * 10 ** (toDecimals - fromDecimals);
} else {
return amount / 10 ** (fromDecimals - toDecimals);
}
}
// ===================================== OPENZEPPELIN'S MATH =====================================
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
// ================================= SOLMATE's FIXEDPOINTMATHLIB =================================
uint256 public constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
{
"compilationTarget": {
"src/base/ERC4626SharePriceOracle.sol": "ERC4626SharePriceOracle"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@balancer-labs/=lib/balancer-v2-monorepo/../../node_modules/@balancer-labs/",
":@balancer/=lib/balancer-v2-monorepo/pkg/",
":@chainlink/=lib/chainlink/",
":@ds-test/=lib/forge-std/lib/ds-test/src/",
":@forge-std/=lib/forge-std/src/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":@solmate/=lib/solmate/src/",
":@uniswap/v3-core/=lib/v3-core/",
":@uniswap/v3-periphery/=lib/v3-periphery/",
":@uniswapV3C/=lib/v3-core/contracts/",
":@uniswapV3P/=lib/v3-periphery/contracts/",
":axelar-gmp-sdk-solidity/=lib/axelar-gmp-sdk-solidity/contracts/",
":balancer-v2-monorepo/=lib/balancer-v2-monorepo/",
":chainlink/=lib/chainlink/integration-tests/contracts/ethereum/src/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":pendle-core-v2-public/=lib/pendle-core-v2-public/contracts/",
":solmate/=lib/solmate/src/",
":v3-core/=lib/v3-core/contracts/",
":v3-periphery/=lib/v3-periphery/contracts/"
]
}
[{"inputs":[{"internalType":"contract ERC4626","name":"_target","type":"address"},{"internalType":"uint64","name":"_heartbeat","type":"uint64"},{"internalType":"uint64","name":"_deviationTrigger","type":"uint64"},{"internalType":"uint64","name":"_gracePeriod","type":"uint64"},{"internalType":"uint16","name":"_observationsToUse","type":"uint16"},{"internalType":"address","name":"_automationRegistry","type":"address"},{"internalType":"uint216","name":"_startingAnswer","type":"uint216"},{"internalType":"uint256","name":"_allowedAnswerChangeLower","type":"uint256"},{"internalType":"uint256","name":"_allowedAnswerChangeUpper","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ERC4626SharePriceOracle__ContractKillSwitch","type":"error"},{"inputs":[],"name":"ERC4626SharePriceOracle__CumulativeTooLarge","type":"error"},{"inputs":[],"name":"ERC4626SharePriceOracle__FuturePerformData","type":"error"},{"inputs":[],"name":"ERC4626SharePriceOracle__NoUpkeepConditionMet","type":"error"},{"inputs":[],"name":"ERC4626SharePriceOracle__OnlyCallableByAutomationRegistry","type":"error"},{"inputs":[],"name":"ERC4626SharePriceOracle__SharePriceTooLarge","type":"error"},{"inputs":[],"name":"ERC4626SharePriceOracle__StalePerformData","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"reportedAnswer","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minAnswer","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxAnswer","type":"uint256"}],"name":"KillSwitchActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"timeUpdated","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeAnswerCalculated","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"latestAnswer","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeWeightedAverageAnswer","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isNotSafeToUse","type":"bool"}],"name":"OracleUpdated","type":"event"},{"inputs":[],"name":"ONE_SHARE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowedAnswerChangeLower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowedAnswerChangeUpper","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"answer","outputs":[{"internalType":"uint216","name":"","type":"uint216"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automationRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"checkUpkeep","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentIndex","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deviationTrigger","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLatest","outputs":[{"internalType":"uint256","name":"ans","type":"uint256"},{"internalType":"uint256","name":"timeWeightedAverageAnswer","type":"uint256"},{"internalType":"bool","name":"notSafeToUse","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLatestAnswer","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gracePeriod","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"heartbeat","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"killSwitch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"observations","outputs":[{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"uint192","name":"cumulative","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"observationsLength","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"performData","type":"bytes"}],"name":"performUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"target","outputs":[{"internalType":"contract ERC4626","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"targetDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]