Contract Name:
OracleScalerFactory
Contract Source Code:
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;
import {OracleScaler} from "silo-oracles/contracts/scaler/OracleScaler.sol";
import {IOracleScalerFactory} from "silo-oracles/contracts/interfaces/IOracleScalerFactory.sol";
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";
contract OracleScalerFactory is IOracleScalerFactory {
mapping(ISiloOracle => bool) public createdInFactory;
/// @inheritdoc IOracleScalerFactory
function createOracleScaler(
address _quoteToken
) external virtual returns (ISiloOracle oracleScaler) {
oracleScaler = new OracleScaler(_quoteToken);
createdInFactory[oracleScaler] = true;
emit OracleScalerCreated(oracleScaler);
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";
import {TokenHelper} from "silo-core/contracts/lib/TokenHelper.sol";
/// @notice OracleScaler is an oracle, which scales the token amounts to 18 decimals instead of original decimals.
/// For example, USDC decimals are 6. 1 USDC is 10**6. This oracle will scale this amount to 10**18. If the token
/// decimals > 18, this oracle will revert.
/// This oracle was created to increase the precision for LTV calculation of low decimal tokens.
contract OracleScaler is ISiloOracle {
/// @dev the amounts will be scaled to 18 decimals.
uint8 public constant DECIMALS_TO_SCALE = 18;
/// @dev token address to use for a quote.
address public immutable QUOTE_TOKEN; // solhint-disable-line var-name-mixedcase
/// @dev scale factor will be multiplied with base token's amount to calculate the scaled value.
uint256 public immutable SCALE_FACTOR; // solhint-disable-line var-name-mixedcase
/// @dev revert if the original token decimals is more or equal 18
error TokenDecimalsTooLarge();
/// @dev revert if the baseToken to quote is not equal to QUOTE_TOKEN
error AssetNotSupported();
constructor(address _quoteToken) {
uint8 quoteTokenDecimals = uint8(TokenHelper.assertAndGetDecimals(_quoteToken));
require(quoteTokenDecimals < DECIMALS_TO_SCALE, TokenDecimalsTooLarge());
SCALE_FACTOR = 10 ** uint256(DECIMALS_TO_SCALE - quoteTokenDecimals);
QUOTE_TOKEN = _quoteToken;
}
// @inheritdoc ISiloOracle
function beforeQuote(address) external virtual {}
// @inheritdoc ISiloOracle
function quote(uint256 _baseAmount, address _baseToken) external virtual view returns (uint256 quoteAmount) {
require(_baseToken == QUOTE_TOKEN, AssetNotSupported());
quoteAmount = _baseAmount * SCALE_FACTOR;
}
// @inheritdoc ISiloOracle
function quoteToken() external virtual view returns (address) {
return address(QUOTE_TOKEN);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";
interface IOracleScalerFactory {
event OracleScalerCreated(ISiloOracle indexed oracleScaler);
/// @notice Create a new oracle scaler
/// @param _quoteToken The quote token for this oracle to support.
/// @return oracleScaler The oracle scaler created
function createOracleScaler(
address _quoteToken
) external returns (ISiloOracle oracleScaler);
}
// 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: 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
// 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: 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;
}
}
// 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);
}