Overview
S Balance
S Value
$0.00| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 31446105 | 237 days ago | Contract Creation | 0 S |
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0xa3f6403375e5b6a4d8a18e302c3a156a9554fa0c
Contract Name:
ChainlinkV3Oracle
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;
import {Initializable} from "openzeppelin5-upgradeable/proxy/utils/Initializable.sol";
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol";
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";
import {OracleNormalization} from "../lib/OracleNormalization.sol";
import {ChainlinkV3OracleConfig} from "./ChainlinkV3OracleConfig.sol";
import {IChainlinkV3Oracle} from "../interfaces/IChainlinkV3Oracle.sol";
contract ChainlinkV3Oracle is IChainlinkV3Oracle, ISiloOracle, Initializable {
ChainlinkV3OracleConfig public oracleConfig;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice validation of config is checked in factory, therefore you should not deploy and initialize directly
/// use factory always.
function initialize(ChainlinkV3OracleConfig _configAddress) external virtual initializer {
oracleConfig = _configAddress;
emit ChainlinkV3ConfigDeployed(_configAddress);
}
/// @inheritdoc ISiloOracle
function quote(uint256 _baseAmount, address _baseToken) external view virtual returns (uint256 quoteAmount) {
ChainlinkV3Config memory config = oracleConfig.getConfig();
if (_baseToken != address(config.baseToken)) revert AssetNotSupported();
if (_baseAmount > type(uint128).max) revert BaseAmountOverflow();
(bool success, uint256 price) = _getAggregatorPrice(config.primaryAggregator, config.primaryHeartbeat);
if (!success) revert InvalidPrice();
if (!config.convertToQuote) {
quoteAmount = OracleNormalization.normalizePrice(
_baseAmount, price, config.normalizationDivider, config.normalizationMultiplier
);
if (quoteAmount == 0) revert ZeroQuote();
return quoteAmount;
}
(
bool secondSuccess,
uint256 secondPrice
) = _getAggregatorPrice(config.secondaryAggregator, config.secondaryHeartbeat);
if (!secondSuccess) revert InvalidSecondPrice();
quoteAmount = OracleNormalization.normalizePrices(
_baseAmount,
price,
secondPrice,
config.normalizationDivider,
config.normalizationMultiplier,
config.invertSecondPrice
);
if (quoteAmount == 0) revert ZeroQuote();
return quoteAmount;
}
/// @dev Returns price directly from aggregator, this method is mostly for debug purposes
function getAggregatorPrice(bool _primary) external view virtual returns (bool success, uint256 price) {
IChainlinkV3Oracle.ChainlinkV3Config memory config = oracleConfig.getConfig();
return _primary
? _getAggregatorPrice(config.primaryAggregator, config.primaryHeartbeat)
: _getAggregatorPrice(config.secondaryAggregator, config.secondaryHeartbeat);
}
/// @inheritdoc ISiloOracle
function quoteToken() external view virtual returns (address) {
IChainlinkV3Oracle.ChainlinkV3Config memory config = oracleConfig.getConfig();
return address(config.quoteToken);
}
function beforeQuote(address) external pure virtual override {
// nothing to execute
}
function _getAggregatorPrice(AggregatorV3Interface _aggregator, uint256 _heartbeat)
internal
view
virtual
returns (bool success, uint256 price)
{
(
/*uint80 roundID*/,
int256 aggregatorPrice,
/*uint256 startedAt*/,
uint256 priceTimestamp,
/*uint80 answeredInRound*/
) = _aggregator.latestRoundData();
// price must be updated at least once every _heartbeat, otherwise something is wrong
uint256 oldestAcceptedPriceTimestamp;
// block.timestamp is more than HEARTBEAT, so we can not underflow
unchecked { oldestAcceptedPriceTimestamp = block.timestamp - _heartbeat; }
if (aggregatorPrice > 0 && priceTimestamp > oldestAcceptedPriceTimestamp) {
return (true, uint256(aggregatorPrice));
}
return (false, 0);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
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);
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);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
interface ISiloOracle {
/// @notice Hook function to call before `quote` function reads price
/// @dev This hook function can be used to change state right before the price is read. For example it can be used
/// for curve read only reentrancy protection. In majority of implementations this will be an empty function.
/// WARNING: reverts are propagated to Silo so if `beforeQuote` reverts, Silo reverts as well.
/// @param _baseToken Address of priced token
function beforeQuote(address _baseToken) external;
/// @return quoteAmount Returns quote price for _baseAmount of _baseToken
/// @param _baseAmount Amount of priced token
/// @param _baseToken Address of priced token
function quote(uint256 _baseAmount, address _baseToken) external view returns (uint256 quoteAmount);
/// @return address of token in which quote (price) is denominated
function quoteToken() external view returns (address);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {TokenHelper} from "silo-core/contracts/lib/TokenHelper.sol";
/// @notice please read carefully unchecked comments, there are some requirements tht must be met in order to not
/// over/under flow
/// @dev Rounding error policy.
/// We're always rounding down by using build-in solidity way for division.
///
/// During normalization we're executing division by `_normalizationDivider` (unless there is multiplicator)
/// and `_secondPrice` (in case second price exist). You can expect rounding errors to be in exclusive range of (0, 1)
/// when doing division. What does it means? This means, that you can be short by up to 1 wei on result.
/// eg. when normalising 12_345 (value with 3 decimals) to 2 decimals representation you lose last digit and end result
/// will be 12_34.
/// What are consequences for protocol?
/// Eg. if 987 of tokens A is worth 12.34 tokens B (after normalization), by losing 0.005 we made tokens A worth a bit
/// more than they really are. If we would round up, then tokens A would be a bit less expensive.
/// Keep in mind we are talking tiny values. There is no argument that can tell which approach is correct.
/// Considering that prices themselves are changing constantly (if you think about it, they are just random numbers
/// close to previous value) and even TWAP price can be manipulated up to some level, if we compare this to rounding
/// error, the rounding error has no meaning at all.
/// Most important part is: how are we using prices in Silo and how rounding error affects the system?
/// We're using prices to calculate LTV. We're deciding how much of token you can borrow but once you borrow you need to
/// repay that amount (plus interest). Price of the token has no influence on how much you repay.
/// Price change by 1 wei can also trigger liquidation, but it will be like switching from eg. 9,99999999999% => 10%.
/// Summing up, rounding error can affect:
/// - max amount of tokens one can borrow
/// - when liquidation happen
/// nn both cases we are talking about 1 wei of difference and this really does not matter to the protocol.
/// It cannot make user to repay less than he borrow and it cannot affect any other operations like deposit,
/// withdraw in a way, that you get less/more tokens.
/// That said, choosing rounding policy is arbitrary decision and our decision is to use default rounding down.
library OracleNormalization {
error Overflow();
/// @notice if you call normalizePrice directly you can create overflow
/// @param _baseAmount amount of base token (can not be higher than uint128!)
/// @param _assetPrice price returned by oracle (can not be higher than uint128!)
/// @param _normalizationDivider constant that allows to translate output price to expected decimals
/// @param _normalizationMultiplier constant that allows to translate output price to expected decimals
/// @return assetPrice uint256 18 decimals price
function normalizePrice(
uint256 _baseAmount,
uint256 _assetPrice,
uint256 _normalizationDivider,
uint256 _normalizationMultiplier
)
internal
pure
returns (uint256 assetPrice)
{
if (_normalizationMultiplier == 0) {
// `_baseAmount * _assetPrice` is safe because we multiply uint128 * uint128
// - _baseAmount is checked in `_quote`
// - _assetPrice is uint128
// div is safe
unchecked { return _baseAmount * _assetPrice / _normalizationDivider; }
}
uint256 mul;
// this is save, check explanation above
unchecked { mul = _baseAmount * _assetPrice; }
return mul * _normalizationMultiplier;
}
/// @notice if you call normalizePrice directly you can create overflow
/// @param _baseAmount amount of base token (can not be higher than uint128!)
/// @param _assetPrice price returned by oracle (can not be higher than uint128!)
/// @param _secondPrice price of quote token denominated in same token as _assetPrice
/// (can not be higher than uint128!)
/// @param _normalizationDivider constant that allows to translate output price to expected decimals
/// @param _normalizationMultiplier constant that allows to translate output price to expected decimals
/// @param _invertSecondPrice if TRUE we have to 1/secondPrice
/// @return assetPrice uint256 18 decimals price
function normalizePrices(
uint256 _baseAmount,
uint256 _assetPrice,
uint256 _secondPrice,
uint256 _normalizationDivider,
uint256 _normalizationMultiplier,
bool _invertSecondPrice
)
internal
pure
returns (uint256 assetPrice)
{
// `_baseAmount * _assetPrice` is safe because we multiply uint128 * uint128
// - _baseAmount is checked in `_quote`, that checks covers `*1e8`, so we sure it is up to uint128
// - _assetPrice is uint128
// however if you call normalizePrice directly (because it is open method) you can create overflow
unchecked { assetPrice = _baseAmount * _assetPrice; }
if (_normalizationMultiplier == 0) {
assetPrice = _invertSecondPrice ? assetPrice / _secondPrice : assetPrice * _secondPrice;
// div is safe
unchecked { assetPrice = assetPrice / _normalizationDivider; }
return assetPrice;
}
assetPrice = assetPrice * _normalizationMultiplier;
return _invertSecondPrice ? assetPrice / _secondPrice : assetPrice * _secondPrice;
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol";
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";
import {IChainlinkV3Oracle} from "../interfaces/IChainlinkV3Oracle.sol";
import {Layer1OracleConfig} from "../_common/Layer1OracleConfig.sol";
contract ChainlinkV3OracleConfig is Layer1OracleConfig {
/// @dev Chainlink aggregator
AggregatorV3Interface internal immutable _AGGREGATOR; // solhint-disable-line var-name-mixedcase
/// @dev secondary Chainlink aggregator to convert price to quote
AggregatorV3Interface internal immutable _SECONDARY_AGGREGATOR; // solhint-disable-line var-name-mixedcase
/// @dev Threshold used to determine if the price returned by the _SECONDARY_AGGREGATOR is valid
uint256 internal immutable _SECONDARY_HEARTBEAT; // solhint-disable-line var-name-mixedcase
/// @dev this can be set to true to convert primary price into price denominated in quote
/// assuming that both AGGREGATORS providing price in the same token
bool internal immutable _CONVERT_TO_QUOTE; // solhint-disable-line var-name-mixedcase
/// @dev If TRUE price will be 1/price
bool internal immutable _INVERT_SECONDARY_PRICE; // solhint-disable-line var-name-mixedcase
/// @dev all verification should be done by factory
constructor(IChainlinkV3Oracle.ChainlinkV3DeploymentConfig memory _config)
Layer1OracleConfig(
_config.baseToken,
_config.quoteToken,
_config.primaryHeartbeat,
_config.normalizationDivider,
_config.normalizationMultiplier
)
{
_AGGREGATOR = _config.primaryAggregator;
_SECONDARY_AGGREGATOR = _config.secondaryAggregator;
_SECONDARY_HEARTBEAT = _config.secondaryHeartbeat;
_CONVERT_TO_QUOTE = address(_config.secondaryAggregator) != address(0);
_INVERT_SECONDARY_PRICE = _config.invertSecondPrice;
}
function getConfig() external view virtual returns (IChainlinkV3Oracle.ChainlinkV3Config memory config) {
config.primaryAggregator = _AGGREGATOR;
config.secondaryAggregator = _SECONDARY_AGGREGATOR;
config.primaryHeartbeat = _HEARTBEAT;
config.secondaryHeartbeat = _SECONDARY_HEARTBEAT;
config.normalizationDivider = _DECIMALS_NORMALIZATION_DIVIDER;
config.normalizationMultiplier = _DECIMALS_NORMALIZATION_MULTIPLIER;
config.baseToken = _BASE_TOKEN;
config.quoteToken = _QUOTE_TOKEN;
config.convertToQuote = _CONVERT_TO_QUOTE;
config.invertSecondPrice = _INVERT_SECONDARY_PRICE;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol";
import {ChainlinkV3OracleConfig} from "../chainlinkV3/ChainlinkV3OracleConfig.sol";
interface IChainlinkV3Oracle {
/// @dev config based on which new oracle will be deployed
/// @notice there is no way to check if aggregators match tokens, so it is users job to verify config.
/// @param primaryAggregator used to read price from chainlink, if it can not provide price in quote token,
/// then you have to setup secondary one that will do the job
/// @param secondaryAggregator if set, it is used translate primary price into quote price eg:
/// primary price is ABC/USD and secondary is ETH/USD, then result will be price in ABC/ETH
/// @param baseToken base token address, it must have decimals() method available
/// @param quoteToken quote toke address, it must have decimals() method available
/// @param primaryHeartbeat heartbeat of primary price
/// @param secondaryHeartbeat heartbeat of secondary price
/// @param normalizationDivider divider that will be used in oracle to normalize price
/// @param normalizationMultiplier multiplier that will be used in oracle to normalize price
/// @param invertSecondPrice in case we using second price, this flag will tell us if we need to 1/secondPrice
struct ChainlinkV3DeploymentConfig {
IERC20Metadata baseToken;
IERC20Metadata quoteToken;
AggregatorV3Interface primaryAggregator;
uint32 primaryHeartbeat;
AggregatorV3Interface secondaryAggregator;
uint32 secondaryHeartbeat;
uint256 normalizationDivider;
uint256 normalizationMultiplier;
bool invertSecondPrice;
}
/// @dev config based on which new oracle will be deployed
/// @notice there is no way to check if aggregators match tokens, so it is users job to verify config.
/// @param primaryAggregator used to read price from chainlink, if it can not provide price in quote token,
/// then you have to setup secondary one that will do the job
/// @param secondaryAggregator if set, it is used translate primary price into quote price eg:
/// primary price is ABC/USD and secondary is ETH/USD, then result will be price in ABC/ETH
/// @param baseToken base token address, it must have decimals() method available
/// @param quoteToken quote toke address, it must have decimals() method available
/// @param primaryHeartbeat heartbeat of primary price
/// @param secondaryHeartbeat heartbeat of secondary price
/// @param invertSecondPrice in case we using second price, this flag will tell us if we need to 1/secondPrice
struct ChainlinkV3Config {
AggregatorV3Interface primaryAggregator;
AggregatorV3Interface secondaryAggregator;
uint256 primaryHeartbeat;
uint256 secondaryHeartbeat;
uint256 normalizationDivider;
uint256 normalizationMultiplier;
IERC20Metadata baseToken;
IERC20Metadata quoteToken;
bool convertToQuote;
bool invertSecondPrice;
}
event ChainlinkV3ConfigDeployed(ChainlinkV3OracleConfig configAddress);
event NewAggregator(address indexed asset, AggregatorV3Interface indexed aggregator, bool convertToQuote);
event NewHeartbeat(address indexed asset, uint256 heartbeat);
event NewQuoteAggregatorHeartbeat(uint256 heartbeat);
event AggregatorDisabled(address indexed asset, AggregatorV3Interface indexed aggregator);
error AddressZero();
error InvalidPrice();
error ZeroQuote();
error InvalidSecondPrice();
error BaseAmountOverflow();
error TokensAreTheSame();
error AggregatorsAreTheSame();
error QuoteTokenNotMatchEth();
error InvalidEthAggregatorDecimals();
error InvalidHeartbeat();
error InvalidEthHeartbeat();
error AssetNotSupported();
error HugeDivider();
error HugeMultiplier();
error MultiplierAndDividerZero();
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {IsContract} from "./IsContract.sol";
library TokenHelper {
uint256 private constant _BYTES32_SIZE = 32;
error TokenIsNotAContract();
function assertAndGetDecimals(address _token) internal view returns (uint256) {
(bool hasMetadata, bytes memory data) =
_tokenMetadataCall(_token, abi.encodeCall(IERC20Metadata.decimals, ()));
// decimals() is optional in the ERC20 standard, so if metadata is not accessible
// we assume there are no decimals and use 0.
if (!hasMetadata) {
return 0;
}
return abi.decode(data, (uint8));
}
/// @dev Returns the symbol for the provided ERC20 token.
/// An empty string is returned if the call to the token didn't succeed.
/// @param _token address of the token to get the symbol for
/// @return assetSymbol the token symbol
function symbol(address _token) internal view returns (string memory assetSymbol) {
(bool hasMetadata, bytes memory data) =
_tokenMetadataCall(_token, abi.encodeCall(IERC20Metadata.symbol, ()));
if (!hasMetadata || data.length == 0) {
return "?";
} else if (data.length == _BYTES32_SIZE) {
return string(removeZeros(data));
} else {
return abi.decode(data, (string));
}
}
/// @dev Removes bytes with value equal to 0 from the provided byte array.
/// @param _data byte array from which to remove zeroes
/// @return result byte array with zeroes removed
function removeZeros(bytes memory _data) internal pure returns (bytes memory result) {
uint256 n = _data.length;
for (uint256 i; i < n; i++) {
if (_data[i] == 0) continue;
result = abi.encodePacked(result, _data[i]);
}
}
/// @dev Performs a staticcall to the token to get its metadata (symbol, decimals, name)
function _tokenMetadataCall(address _token, bytes memory _data) private view returns (bool, bytes memory) {
// We need to do this before the call, otherwise the call will succeed even for EOAs
require(IsContract.isContract(_token), TokenIsNotAContract());
(bool success, bytes memory result) = _token.staticcall(_data);
// If the call reverted we assume the token doesn't follow the metadata extension
if (!success) {
return (false, "");
}
return (true, result);
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
/// @notice to keep config contract size low (this is the one that will be deployed each time)
/// factory contract take over verification. You should not deploy or use config that was not created by factory.
/// @dev This is common config for Layer1 oracles
abstract contract Layer1OracleConfig {
/// @dev price must be updated at least once every `_HEARTBEAT` seconds, otherwise something is wrong
uint256 internal immutable _HEARTBEAT; // solhint-disable-line var-name-mixedcase
/// @dev constant used for normalising price
uint256 internal immutable _DECIMALS_NORMALIZATION_DIVIDER; // solhint-disable-line var-name-mixedcase
/// @dev constant used for normalising price
uint256 internal immutable _DECIMALS_NORMALIZATION_MULTIPLIER; // solhint-disable-line var-name-mixedcase
IERC20Metadata internal immutable _BASE_TOKEN; // solhint-disable-line var-name-mixedcase
IERC20Metadata internal immutable _QUOTE_TOKEN; // solhint-disable-line var-name-mixedcase
/// @dev all verification should be done by factory
constructor(
IERC20Metadata _baseToken,
IERC20Metadata _quoteToken,
uint256 _heartbeat,
uint256 _normalizationDivider,
uint256 _normalizationMultiplier
) {
_DECIMALS_NORMALIZATION_DIVIDER = _normalizationDivider;
_DECIMALS_NORMALIZATION_MULTIPLIER = _normalizationMultiplier;
_BASE_TOKEN = _baseToken;
_QUOTE_TOKEN = _quoteToken;
_HEARTBEAT = _heartbeat;
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;
library IsContract {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address _account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return _account.code.length > 0;
}
}{
"remappings": [
"forge-std/=gitmodules/forge-std/src/",
"silo-foundry-utils/=gitmodules/silo-foundry-utils/contracts/",
"properties/=gitmodules/crytic/properties/contracts/",
"silo-core/=silo-core/",
"silo-oracles/=silo-oracles/",
"silo-vaults/=silo-vaults/",
"ve-silo/=ve-silo/",
"@openzeppelin/=gitmodules/openzeppelin-contracts-5/",
"morpho-blue/=gitmodules/morpho-blue/src/",
"openzeppelin5/=gitmodules/openzeppelin-contracts-5/contracts/",
"openzeppelin5-upgradeable/=gitmodules/openzeppelin-contracts-upgradeable-5/contracts/",
"chainlink/=gitmodules/chainlink/contracts/src/",
"chainlink-ccip/=gitmodules/chainlink-ccip/contracts/src/",
"uniswap/=gitmodules/uniswap/",
"@uniswap/v3-core/=gitmodules/uniswap/v3-core/",
"balancer-labs/v2-solidity-utils/=external/balancer-v2-monorepo/pkg/solidity-utils/contracts/",
"balancer-labs/v2-interfaces/=external/balancer-v2-monorepo/pkg/interfaces/contracts/",
"balancer-labs/v2-liquidity-mining/=external/balancer-v2-monorepo/pkg/liquidity-mining/contracts/",
"pyth-sdk-solidity/=gitmodules/pyth-sdk-solidity/target_chains/ethereum/sdk/solidity/",
"a16z-erc4626-tests/=gitmodules/a16z-erc4626-tests/",
"ERC4626/=gitmodules/crytic/properties/lib/ERC4626/contracts/",
"createx/=gitmodules/pyth-sdk-solidity/lazer/contracts/evm/lib/createx/src/",
"crytic/=gitmodules/crytic/",
"ds-test/=gitmodules/openzeppelin-contracts-5/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=gitmodules/openzeppelin-contracts-5/lib/erc4626-tests/",
"halmos-cheatcodes/=gitmodules/morpho-blue/lib/halmos-cheatcodes/src/",
"openzeppelin-contracts-5/=gitmodules/openzeppelin-contracts-5/",
"openzeppelin-contracts-upgradeable-5/=gitmodules/openzeppelin-contracts-upgradeable-5/",
"openzeppelin-contracts-upgradeable/=gitmodules/pyth-sdk-solidity/lazer/contracts/evm/lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=gitmodules/openzeppelin-contracts-upgradeable-5/lib/openzeppelin-contracts/",
"solady/=gitmodules/pyth-sdk-solidity/lazer/contracts/evm/lib/createx/lib/solady/",
"solmate/=gitmodules/crytic/properties/lib/solmate/src/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false,
"libraries": {}
}Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressZero","type":"error"},{"inputs":[],"name":"AggregatorsAreTheSame","type":"error"},{"inputs":[],"name":"AssetNotSupported","type":"error"},{"inputs":[],"name":"BaseAmountOverflow","type":"error"},{"inputs":[],"name":"HugeDivider","type":"error"},{"inputs":[],"name":"HugeMultiplier","type":"error"},{"inputs":[],"name":"InvalidEthAggregatorDecimals","type":"error"},{"inputs":[],"name":"InvalidEthHeartbeat","type":"error"},{"inputs":[],"name":"InvalidHeartbeat","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidPrice","type":"error"},{"inputs":[],"name":"InvalidSecondPrice","type":"error"},{"inputs":[],"name":"MultiplierAndDividerZero","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"QuoteTokenNotMatchEth","type":"error"},{"inputs":[],"name":"TokensAreTheSame","type":"error"},{"inputs":[],"name":"ZeroQuote","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"contract AggregatorV3Interface","name":"aggregator","type":"address"}],"name":"AggregatorDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract ChainlinkV3OracleConfig","name":"configAddress","type":"address"}],"name":"ChainlinkV3ConfigDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"contract AggregatorV3Interface","name":"aggregator","type":"address"},{"indexed":false,"internalType":"bool","name":"convertToQuote","type":"bool"}],"name":"NewAggregator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"heartbeat","type":"uint256"}],"name":"NewHeartbeat","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"heartbeat","type":"uint256"}],"name":"NewQuoteAggregatorHeartbeat","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"beforeQuote","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"_primary","type":"bool"}],"name":"getAggregatorPrice","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ChainlinkV3OracleConfig","name":"_configAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"oracleConfig","outputs":[{"internalType":"contract ChainlinkV3OracleConfig","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_baseAmount","type":"uint256"},{"internalType":"address","name":"_baseToken","type":"address"}],"name":"quote","outputs":[{"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"quoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in S
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.