Overview
S Balance
0 S
S Value
$0.00More Info
Private Name Tags
ContractCreator
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
4234070 | 22 days ago | Contract Creation | 0 S |
Loading...
Loading
Minimal Proxy Contract for 0x9b2a312858043b918959f9a21ca89ec666a8afd3
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 ); 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 /// @return assetPrice uint256 18 decimals price function normalizePrices( uint256 _baseAmount, uint256 _assetPrice, uint256 _secondPrice, 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`, 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 // div is safe unchecked { return _baseAmount * _assetPrice / _normalizationDivider / _secondPrice; } } uint256 mul; // this is save, check explanation above unchecked { mul = _baseAmount * _assetPrice; } mul = mul * _normalizationMultiplier; // div is safe unchecked { return mul / _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 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); } 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; } }
// 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 struct ChainlinkV3DeploymentConfig { IERC20Metadata baseToken; IERC20Metadata quoteToken; AggregatorV3Interface primaryAggregator; uint32 primaryHeartbeat; AggregatorV3Interface secondaryAggregator; uint32 secondaryHeartbeat; uint256 normalizationDivider; uint256 normalizationMultiplier; } /// @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 struct ChainlinkV3Config { AggregatorV3Interface primaryAggregator; AggregatorV3Interface secondaryAggregator; uint256 primaryHeartbeat; uint256 secondaryHeartbeat; uint256 normalizationDivider; uint256 normalizationMultiplier; IERC20Metadata baseToken; IERC20Metadata quoteToken; bool convertToQuote; } 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/contracts/", "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/", "@balancer-labs/=node_modules/@balancer-labs/", "@ensdomains/=node_modules/@ensdomains/", "@openzeppelin/contracts-upgradeable/=gitmodules/openzeppelin-contracts-upgradeable-5/contracts/", "@openzeppelin/contracts/=gitmodules/openzeppelin-contracts-5/contracts/", "@solidity-parser/=node_modules/@solidity-parser/", "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/", "hardhat/=node_modules/hardhat/", "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/", "prettier-plugin-solidity/=node_modules/prettier-plugin-solidity/", "proposals/=node_modules/proposals/", "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": {} }
[{"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
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ 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.