Source Code
Overview
HYPE Balance
HYPE Value
$0.00Latest 16 from a total of 16 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Accept Ltv Updat... | 4000543 | 252 days ago | IN | 0 HYPE | 0.00000472 | ||||
| Request Ltv Upda... | 3878217 | 255 days ago | IN | 0 HYPE | 0.00000966 | ||||
| Accept Ltv Updat... | 3645509 | 260 days ago | IN | 0 HYPE | 0.00000708 | ||||
| Request Ltv Upda... | 3645509 | 260 days ago | IN | 0 HYPE | 0.00001062 | ||||
| Accept Ltv Updat... | 3564165 | 262 days ago | IN | 0 HYPE | 0.00001089 | ||||
| Request Ltv Upda... | 3564165 | 262 days ago | IN | 0 HYPE | 0.00001633 | ||||
| Transfer Ownersh... | 1328615 | 312 days ago | IN | 0 HYPE | 0.00000286 | ||||
| Accept Ltv Updat... | 1296871 | 313 days ago | IN | 0 HYPE | 0.00000481 | ||||
| Accept Ltv Updat... | 1296716 | 313 days ago | IN | 0 HYPE | 0.00000439 | ||||
| Request Ltv Upda... | 1252174 | 314 days ago | IN | 0 HYPE | 0.00000966 | ||||
| Accept Ltv Updat... | 1016432 | 319 days ago | IN | 0 HYPE | 0.00000643 | ||||
| Request Ltv Upda... | 1016432 | 319 days ago | IN | 0 HYPE | 0.00000966 | ||||
| Set Oracle | 1015657 | 319 days ago | IN | 0 HYPE | 0.00000481 | ||||
| Set Oracle | 1015595 | 319 days ago | IN | 0 HYPE | 0.00000577 | ||||
| Update From Regi... | 1015378 | 319 days ago | IN | 0 HYPE | 0.00000941 | ||||
| Transfer Ownersh... | 1014789 | 319 days ago | IN | 0 HYPE | 0.00000309 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Contract Name:
RiskEngine
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 1024 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// types
import { Pool } from "./Pool.sol";
import { AssetData, DebtData } from "./PositionManager.sol";
import { Registry } from "./Registry.sol";
import { RiskModule } from "./RiskModule.sol";
import { IOracle } from "./interfaces/IOracle.sol";
// contracts
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/// @title RiskEngine
contract RiskEngine is Ownable {
/// @notice Timelock delay to update asset LTVs
uint256 public constant TIMELOCK_DURATION = 24 * 60 * 60; // 24 hours
/// @notice Timelock deadline to enforce timely updates
uint256 public constant TIMELOCK_DEADLINE = 3 * 24 * 60 * 60; // 72 hours
/// @notice Sentiment Pool registry key hash
/// @dev keccak(SENTIMENT_POOL_KEY)
bytes32 public constant SENTIMENT_POOL_KEY = 0x1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c09728;
/// @notice Sentiment Risk Module registry key hash
/// @dev keccak(SENTIMENT_RISK_MODULE_KEY)
bytes32 public constant SENTIMENT_RISK_MODULE_KEY =
0x881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c77;
/// @title LtvUpdate
/// @notice Utility struct to store pending Pool LTV updates
struct LtvUpdate {
uint256 ltv;
uint256 validAfter;
}
// Protocol LTV configs:
// - pool owners are free to configure a different LTV for every asset on their pool
// - however these custom LTVs must fall within the global protocol limits
// - the global LTV limits can only be modified by the protocol owner
// - ltv updates will revert if they fall outside of the protocol bounds
/// @notice Minimum LTV bound
uint256 public minLtv;
/// @notice Maximum LTV bound
uint256 public maxLtv;
/// @notice Sentiment Registry
Registry public registry;
/// @notice Sentiment Singleton Pool
Pool public pool;
/// @notice Sentiment Risk Module
RiskModule public riskModule;
/// @dev Asset to Oracle mapping
mapping(address asset => address oracle) public oracleFor;
/// @notice Fetch the ltv for a given asset in a pool
mapping(uint256 poolId => mapping(address asset => uint256 ltv)) public ltvFor;
/// @notice Fetch pending LTV update details for a given pool and asset pair, if any
mapping(uint256 poolId => mapping(address asset => LtvUpdate ltvUpdate)) public ltvUpdateFor;
/// @notice Check if poolA lends to positions that also borrow from poolB
mapping(uint256 poolA => mapping(uint256 poolB => bool isAllowed)) public isAllowedPair;
/// @notice Pool address was updated
event PoolSet(address pool);
/// @notice Registry address was updated
event RegistrySet(address registry);
/// @notice Risk Module address was updated
event RiskModuleSet(address riskModule);
/// @notice Protocol LTV bounds were updated
event LtvBoundsSet(uint256 minLtv, uint256 maxLtv);
/// @notice Oracle associated with an asset was updated
event OracleSet(address indexed asset, address oracle);
/// @notice Pending LTV update was rejected
event LtvUpdateRejected(uint256 indexed poolId, address indexed asset);
/// @notice Pending LTV update was accepted
event LtvUpdateAccepted(uint256 indexed poolId, address indexed asset, uint256 ltv);
/// @notice LTV update was requested
event LtvUpdateRequested(uint256 indexed poolId, address indexed asset, LtvUpdate ltvUpdate);
/// @notice Allowed base pool pair toggled
event PoolPairToggled(uint256 indexed poolA, uint256 indexed poolB, bool isAllowed);
/// @notice There is no oracle associated with the given asset
error RiskEngine_NoOracleFound(address asset);
/// @notice Proposed LTV is outside of protocol LTV bounds
error RiskEngine_LtvLimitBreached(uint256 ltv);
/// @notice There is no pending LTV update for the given Pool-Asset pair
error RiskEngine_NoLtvUpdate(uint256 poolId, address asset);
/// @notice Function access is restricted to the owner of the pool
error RiskEngine_OnlyPoolOwner(uint256 poolId, address sender);
/// @notice Timelock delay for the pending LTV update has not been completed
error RiskEngine_LtvUpdateTimelocked(uint256 poolId, address asset);
/// @notice Timelock deadline for LTV update has passed
error RiskEngine_LtvUpdateExpired(uint256 poolId, address asset);
/// @notice Global min ltv cannot be zero
error RiskEngine_MinLtvTooLow();
/// @notice Global max ltv must be less than 100%
error RiskEngine_MaxLtvTooHigh();
/// @notice Pool LTV for the asset being lent out must be zero
error RiskEngine_CannotBorrowPoolAsset(uint256 poolId);
/// @notice Min Ltv is not less than Max Ltv
error RiskEngine_InvalidLtvLimits(uint256 minLtv, uint256 maxLtv);
/// @notice Base pool has not been initialized
error RiskEngine_InvalidBasePool(uint256 poolId);
/// @param registry_ Sentiment Registry
/// @param minLtv_ Minimum LTV bound
/// @param maxLtv_ Maximum LTV bound
constructor(address registry_, uint256 minLtv_, uint256 maxLtv_) Ownable() {
if (minLtv_ == 0) revert RiskEngine_MinLtvTooLow();
if (maxLtv_ >= 1e18) revert RiskEngine_MaxLtvTooHigh();
if (minLtv_ >= maxLtv_) revert RiskEngine_InvalidLtvLimits(minLtv_, maxLtv_);
registry = Registry(registry_);
minLtv = minLtv_;
maxLtv = maxLtv_;
emit LtvBoundsSet(minLtv_, maxLtv_);
}
/// @notice Fetch and update module addreses from the registry
function updateFromRegistry() external {
pool = Pool(registry.addressFor(SENTIMENT_POOL_KEY));
riskModule = RiskModule(registry.addressFor(SENTIMENT_RISK_MODULE_KEY));
emit PoolSet(address(pool));
emit RiskModuleSet(address(riskModule));
}
/// @notice Fetch value of given asset amount in ETH
function getValueInEth(address asset, uint256 amt) public view returns (uint256) {
if (amt == 0) return 0;
address oracle = oracleFor[asset];
if (oracle == address(0)) revert RiskEngine_NoOracleFound(asset);
return IOracle(oracle).getValueInEth(asset, amt);
}
/// @notice Fetch position health factor
function getPositionHealthFactor(address position) external view returns (uint256) {
return riskModule.getPositionHealthFactor(position);
}
/// @notice Validate liquidator data and value of assets seized
function validateLiquidation(
address position,
DebtData[] calldata debtData,
AssetData[] calldata assetData
)
external
view
returns (uint256, uint256, DebtData[] memory, AssetData[] memory)
{
return riskModule.validateLiquidation(position, debtData, assetData);
}
/// @notice Validate liquidator data for assets to be repaid
function validateBadDebtLiquidation(
address position,
DebtData[] calldata debtData
)
external
view
returns (DebtData[] memory, AssetData[] memory)
{
return riskModule.validateBadDebtLiquidation(position, debtData);
}
/// @notice Fetch risk-associated data for a given position
/// @param position The address of the position to get the risk data for
/// @return totalAssetValue The total asset value of the position
/// @return totalDebtValue The total debt value of the position
/// @return minReqAssetValue The minimum required asset value for the position to be healthy
function getRiskData(address position) external view returns (uint256, uint256, uint256) {
return riskModule.getRiskData(position);
}
/// @notice Allow poolA to lend against positions that also borrow from poolB
/// @dev When toggled or untoggled, only applies to future borrows
function toggleAllowedPoolPair(uint256 poolA, uint256 poolB) external {
if (pool.ownerOf(poolA) != msg.sender) revert RiskEngine_OnlyPoolOwner(poolA, msg.sender);
if (pool.ownerOf(poolB) == address(0)) revert RiskEngine_InvalidBasePool(poolB);
isAllowedPair[poolA][poolB] = !isAllowedPair[poolA][poolB];
emit PoolPairToggled(poolA, poolB, isAllowedPair[poolA][poolB]);
}
/// @notice Propose an LTV update for a given Pool-Asset pair
/// @dev overwrites any pending or expired updates
function requestLtvUpdate(uint256 poolId, address asset, uint256 ltv) external {
if (msg.sender != pool.ownerOf(poolId)) revert RiskEngine_OnlyPoolOwner(poolId, msg.sender);
// set oracle before ltv so risk modules don't have to explicitly check if an oracle exists
if (oracleFor[asset] == address(0)) revert RiskEngine_NoOracleFound(asset);
// ensure new ltv is within global limits. also enforces that an existing ltv cannot be updated to zero
if (ltv < minLtv || ltv > maxLtv) revert RiskEngine_LtvLimitBreached(ltv);
// Positions cannot borrow against the same asset that is being lent out
if (pool.getPoolAssetFor(poolId) == asset) revert RiskEngine_CannotBorrowPoolAsset(poolId);
LtvUpdate memory ltvUpdate;
// only modification of previously set ltvs require a timelock
if (ltvFor[poolId][asset] == 0) ltvUpdate = LtvUpdate({ ltv: ltv, validAfter: block.timestamp });
else ltvUpdate = LtvUpdate({ ltv: ltv, validAfter: block.timestamp + TIMELOCK_DURATION });
ltvUpdateFor[poolId][asset] = ltvUpdate;
emit LtvUpdateRequested(poolId, asset, ltvUpdate);
}
/// @notice Apply a pending LTV update
function acceptLtvUpdate(uint256 poolId, address asset) external {
if (msg.sender != pool.ownerOf(poolId)) revert RiskEngine_OnlyPoolOwner(poolId, msg.sender);
if (oracleFor[asset] == address(0)) revert RiskEngine_NoOracleFound(asset);
LtvUpdate memory ltvUpdate = ltvUpdateFor[poolId][asset];
// revert if there is no pending update
if (ltvUpdate.validAfter == 0) revert RiskEngine_NoLtvUpdate(poolId, asset);
// revert if called before timelock delay has passed
if (ltvUpdate.validAfter > block.timestamp) revert RiskEngine_LtvUpdateTimelocked(poolId, asset);
// revert if timelock deadline has passed
if (block.timestamp > ltvUpdate.validAfter + TIMELOCK_DEADLINE) {
revert RiskEngine_LtvUpdateExpired(poolId, asset);
}
// apply changes
ltvFor[poolId][asset] = ltvUpdate.ltv;
delete ltvUpdateFor[poolId][asset];
emit LtvUpdateAccepted(poolId, asset, ltvUpdate.ltv);
}
/// @notice Reject a pending LTV update
function rejectLtvUpdate(uint256 poolId, address asset) external {
if (msg.sender != pool.ownerOf(poolId)) revert RiskEngine_OnlyPoolOwner(poolId, msg.sender);
delete ltvUpdateFor[poolId][asset];
emit LtvUpdateRejected(poolId, asset);
}
/// @notice Set Protocol LTV bounds
function setLtvBounds(uint256 _minLtv, uint256 _maxLtv) external onlyOwner {
if (_minLtv == 0) revert RiskEngine_MinLtvTooLow();
if (_maxLtv >= 1e18) revert RiskEngine_MaxLtvTooHigh();
if (_minLtv >= _maxLtv) revert RiskEngine_InvalidLtvLimits(_minLtv, _maxLtv);
minLtv = _minLtv;
maxLtv = _maxLtv;
emit LtvBoundsSet(_minLtv, _maxLtv);
}
/// @notice Set the oracle address used to price a given asset
/// @dev Does not support ERC777s, rebasing and fee-on-transfer tokens
function setOracle(address asset, address oracle) external onlyOwner {
oracleFor[asset] = oracle;
emit OracleSet(asset, oracle);
}
/// @notice Update the registry associated with this Risk Engine
function setRegistry(address newRegistry) external onlyOwner {
registry = Registry(newRegistry);
emit RegistrySet(newRegistry);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @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;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @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 Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 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 functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_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 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_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() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @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 {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @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;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*
* _Available since v4.8.3._
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
pragma solidity ^0.8.2;
import "../beacon/IBeacon.sol";
import "../../interfaces/IERC1967.sol";
import "../../interfaces/draft-IERC1822.sol";
import "../../utils/Address.sol";
import "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*
* _Available since v4.1._
*/
abstract contract ERC1967Upgrade is IERC1967 {
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation address.
*/
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Perform implementation upgrade
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Perform implementation upgrade with additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
_upgradeTo(newImplementation);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
// Upgrades from old implementations will perform a rollback test. This test requires the new
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
// this special case will break upgrade paths from old UUPS implementation to new ones.
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
_setImplementation(newImplementation);
} else {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
} catch {
revert("ERC1967Upgrade: new implementation is not UUPS");
}
_upgradeToAndCall(newImplementation, data, forceCall);
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _changeAdmin(address newAdmin) internal {
emit AdminChanged(_getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
*/
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function _getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
require(
Address.isContract(IBeacon(newBeacon).implementation()),
"ERC1967: beacon implementation is not a contract"
);
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive() external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol)
pragma solidity ^0.8.0;
import "./IBeacon.sol";
import "../Proxy.sol";
import "../ERC1967/ERC1967Upgrade.sol";
/**
* @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
*
* The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
* conflict with the storage layout of the implementation behind the proxy.
*
* _Available since v3.4._
*/
contract BeaconProxy is Proxy, ERC1967Upgrade {
/**
* @dev Initializes the proxy with `beacon`.
*
* If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
* will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity
* constructor.
*
* Requirements:
*
* - `beacon` must be a contract with the interface {IBeacon}.
*/
constructor(address beacon, bytes memory data) payable {
_upgradeBeaconToAndCall(beacon, data, false);
}
/**
* @dev Returns the current beacon address.
*/
function _beacon() internal view virtual returns (address) {
return _getBeacon();
}
/**
* @dev Returns the current implementation address of the associated beacon.
*/
function _implementation() internal view virtual override returns (address) {
return IBeacon(_getBeacon()).implementation();
}
/**
* @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}.
*
* If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon.
*
* Requirements:
*
* - `beacon` must be a contract.
* - The implementation returned by `beacon` must be a contract.
*/
function _setBeacon(address beacon, bytes memory data) internal virtual {
_upgradeBeaconToAndCall(beacon, data, false);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
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 amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.0;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
* _Available since v4.9 for `string`, `bytes`._
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// types
import { Registry } from "./Registry.sol";
import { RiskEngine } from "./RiskEngine.sol";
import { IOracle } from "./interfaces/IOracle.sol";
import { IRateModel } from "./interfaces/IRateModel.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// libraries
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
// contracts
import { ERC6909 } from "./lib/ERC6909.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
/// @title Pool
/// @notice Singleton pool for all pools that superpools lend to and positions borrow from
contract Pool is OwnableUpgradeable, PausableUpgradeable, ERC6909 {
using Math for uint256;
using SafeERC20 for IERC20;
address private constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
/// @notice Maximum amount of deposit shares per base pool
uint256 public constant MAX_DEPOSIT_SHARES = type(uint112).max;
/// @notice Maximum amount of borrow shares per base pool
uint256 public constant MAX_BORROW_SHARES = type(uint112).max;
/// @notice Minimum amount of initial shares to be burned
uint256 public constant MIN_BURNED_SHARES = 1_000_000;
/// @notice Timelock delay for pool rate model modification
uint256 public constant TIMELOCK_DURATION = 24 * 60 * 60; // 24 hours
/// @notice Timelock deadline to enforce timely updates
uint256 public constant TIMELOCK_DEADLINE = 3 * 24 * 60 * 60; // 72 hours
/// @notice Registry key hash for the Sentiment position manager
/// @dev keccak(SENTIMENT_POSITION_MANAGER_KEY)
bytes32 public constant SENTIMENT_POSITION_MANAGER_KEY =
0xd4927490fbcbcafca716cca8e8c8b7d19cda785679d224b14f15ce2a9a93e148;
/// @notice Sentiment risk engine registry key
/// @dev keccak(SENTIMENT_RISK_ENGINE_KEY)
bytes32 public constant SENTIMENT_RISK_ENGINE_KEY =
0x5b6696788621a5d6b5e3b02a69896b9dd824ebf1631584f038a393c29b6d7555;
/// @notice Initial interest fee for pools
uint256 public defaultInterestFee;
/// @notice Initial origination fee for pools
uint256 public defaultOriginationFee;
/// @notice Sentiment registry
address public registry;
/// @notice Sentiment fee receiver
address public feeRecipient;
/// @notice Sentiment position manager
address public positionManager;
/// @notice Sentiment Risk Engine
address public riskEngine;
/// @notice minimum amount that must be borrowed in a single operation
uint256 public minBorrow; // in eth
/// @notice minimum debt that a borrower must maintain
uint256 public minDebt; // in eth
/// @notice Fetch the owner for a given pool id
mapping(uint256 poolId => address poolOwner) public ownerOf;
/// @notice Fetch debt owed by a given position for a particular pool, denominated in borrow shares
mapping(uint256 poolId => mapping(address position => uint256 borrowShares)) public borrowSharesOf;
/// @title PoolData
/// @notice Pool config and state container
struct PoolData {
bool isPaused;
address asset;
address rateModel;
uint256 borrowCap;
uint256 depositCap;
uint256 lastUpdated;
uint256 interestFee;
uint256 originationFee;
uint256 totalBorrowAssets;
uint256 totalBorrowShares;
uint256 totalDepositAssets;
uint256 totalDepositShares;
}
/// @notice Fetch pool config and state for a given pool id
mapping(uint256 poolId => PoolData data) public poolDataFor;
/// @title RateModelUpdate
/// @notice Utility struct to store pending pool rate model updates
struct RateModelUpdate {
address rateModel;
uint256 validAfter;
}
/// @notice Fetch pending rate model updates for a given pool id
mapping(uint256 poolId => RateModelUpdate rateModelUpdate) public rateModelUpdateFor;
/// @notice Position Manager addresss was updated
event PositionManagerSet(address positionManager);
/// @notice Risk Engine address was updated
event RiskEngineSet(address riskEngine);
/// @notice Minimum debt amount set
event MinDebtSet(uint256 minDebt);
/// @notice Minimum borrow amount set
event MinBorrowSet(uint256 minBorrow);
/// @notice Registry address was set
event RegistrySet(address registry);
/// @notice Pool fee recipient set
event FeeRecipientSet(address feeRecipient);
/// @notice Paused state of a pool was toggled
event PoolPauseToggled(uint256 poolId, bool paused);
/// @notice Asset cap for a pool was set
event PoolCapSet(uint256 indexed poolId, uint256 poolCap);
/// @notice Borrow debt ceiling for a pool was set
event BorrowCapSet(uint256 indexed poolId, uint256 borrowCap);
/// @notice Owner for a pool was set
event PoolOwnerSet(uint256 indexed poolId, address owner);
/// @notice Rate model for a pool was updated
event RateModelUpdated(uint256 indexed poolId, address rateModel);
/// @notice Interest fee for a pool was updated
event InterestFeeSet(uint256 indexed poolId, uint256 interestFee);
/// @notice Origination fee for a pool was updated
event OriginationFeeSet(uint256 indexed poolId, uint256 originationFee);
/// @notice Pending rate model update for a pool was rejected
event RateModelUpdateRejected(uint256 indexed poolId, address rateModel);
/// @notice Rate model update for a pool was proposed
event RateModelUpdateRequested(uint256 indexed poolId, address rateModel);
/// @notice Default interest fee for new pools updated
event DefaultInterestFeeSet(uint256 defaultInterestFee);
/// @notice Default origination fee for new pools updated
event DefaultOriginationFeeSet(uint256 defaultOriginationFee);
/// @notice New pool was initialized
event PoolInitialized(uint256 indexed poolId, address indexed owner, address indexed asset);
/// @notice Assets were deposited to a pool
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
/// @notice Debt was repaid from a position to a pool
event Repay(address indexed position, uint256 indexed poolId, address indexed asset, uint256 amount);
/// @notice Assets were borrowed from a position to a pool
event Borrow(address indexed position, uint256 indexed poolId, address indexed asset, uint256 amount);
/// @notice Assets were withdrawn from a pool
event Withdraw(
address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
/// @notice Given fee value is greater than 100%
error Pool_FeeTooHigh();
/// @notice Zero address cannot be the pool owner
error Pool_ZeroAddressOwner();
/// @notice Pool is paused
error Pool_PoolPaused(uint256 poolId);
/// @notice Total Base Pool shares exceeds MAX_DEPOSIT_SHARES
error Pool_MaxDepositShares(uint256 poolId);
/// @notice Total borrow shares exceed MAX_BORROW_SHARES
error Pool_MaxBorrowShares(uint256 poolId);
/// @notice Pool borrow cap exceeded
error Pool_BorrowCapExceeded(uint256 poolId);
/// @notice Pool deposits exceed asset cap
error Pool_PoolCapExceeded(uint256 poolId);
/// @notice No pending rate model update for the pool
error Pool_NoRateModelUpdate(uint256 poolId);
/// @notice Attempt to initialize an already existing pool
error Pool_PoolAlreadyInitialized(uint256 poolId);
/// @notice Attempt to redeem zero shares worth of assets from the pool
error Pool_ZeroShareWithdraw(uint256 poolId, uint256 assets);
/// @notice Attempt to repay zero shares worth of assets to the pool
error Pool_ZeroSharesRepay(uint256 poolId, uint256 amt);
/// @notice Attempt to borrow zero shares worth of assets from the pool
error Pool_ZeroSharesBorrow(uint256 poolId, uint256 amt);
/// @notice Attempt to deposit zero shares worth of assets to the pool
error Pool_ZeroSharesDeposit(uint256 poolId, uint256 amt);
/// @notice Function access restricted only to the pool owner
error Pool_OnlyPoolOwner(uint256 poolId, address sender);
/// @notice Function access restricted only to Sentiment Position Manager
error Pool_OnlyPositionManager(uint256 poolId, address sender);
/// @notice Insufficient pool liquidity to service borrow
error Pool_InsufficientBorrowLiquidity(uint256 poolId, uint256 assetsInPool, uint256 assets);
/// @notice Insufficient pool liquidity to service withdrawal
error Pool_InsufficientWithdrawLiquidity(uint256 poolId, uint256 assetsInPool, uint256 assets);
/// @notice Rate model timelock delay has not been completed
error Pool_TimelockPending(uint256 poolId, uint256 currentTimestamp, uint256 validAfter);
/// @notice Rate model timelock deadline has passed
error Pool_TimelockExpired(uint256 poolId, uint256 currentTimestamp, uint256 validAfter);
/// @notice Rate model was not found in the Sentiment registry
error Pool_RateModelNotFound(bytes32 rateModelKey);
/// @notice Borrowed amount is lower than minimum borrow amount
error Pool_BorrowAmountTooLow(uint256 poolId, address asset, uint256 amt);
/// @notice Debt is below min debt amount
error Pool_DebtTooLow(uint256 poolId, address asset, uint256 amt);
/// @notice No oracle found for pool asset
error Pool_OracleNotFound(address asset);
/// @notice Fee recipient must be non-zero
error Pool_ZeroFeeRecipient();
/// @notice Pool has zero assets and non-zero shares
error Pool_ZeroAssetsNonZeroShares(uint256 poolId);
/// @notice Less than MIN_BURNED_SHARES burned during pool initialization
error Pool_MinBurnedShares(uint256 shares);
constructor() {
_disableInitializers();
}
/// @notice Initializer for TransparentUpgradeableProxy
/// @param owner_ Pool owner
/// @param registry_ Sentiment Registry
/// @param feeRecipient_ Sentiment fee receiver
function initialize(
address owner_,
address registry_,
address feeRecipient_,
uint256 minDebt_,
uint256 minBorrow_,
uint256 defaultInterestFee_,
uint256 defaultOriginationFee_
)
public
initializer
{
_transferOwnership(owner_);
if (defaultInterestFee_ > 1e18) revert Pool_FeeTooHigh();
if (defaultOriginationFee_ > 1e18) revert Pool_FeeTooHigh();
if (feeRecipient_ == address(0)) revert Pool_ZeroFeeRecipient();
defaultInterestFee = defaultInterestFee_;
defaultOriginationFee = defaultOriginationFee_;
registry = registry_;
feeRecipient = feeRecipient_;
minBorrow = minBorrow_;
minDebt = minDebt_;
updateFromRegistry();
}
/// @notice Fetch and update module addreses from the registry
function updateFromRegistry() public {
positionManager = Registry(registry).addressFor(SENTIMENT_POSITION_MANAGER_KEY);
riskEngine = Registry(registry).addressFor(SENTIMENT_RISK_ENGINE_KEY);
emit PositionManagerSet(positionManager);
emit RiskEngineSet(riskEngine);
}
/// @notice Fetch amount of liquid assets currently held in a given pool
function getLiquidityOf(uint256 poolId) public view returns (uint256) {
PoolData storage pool = poolDataFor[poolId];
uint256 assetsInPool = pool.totalDepositAssets - pool.totalBorrowAssets;
uint256 totalBalance = IERC20(pool.asset).balanceOf(address(this));
return (totalBalance > assetsInPool) ? assetsInPool : totalBalance;
}
/// @notice Fetch pool asset balance for depositor to a pool
function getAssetsOf(uint256 poolId, address guy) public view returns (uint256) {
PoolData storage pool = poolDataFor[poolId];
(uint256 accruedInterest, uint256 feeShares) = simulateAccrue(pool);
return _convertToAssets(
balanceOf[guy][poolId],
pool.totalDepositAssets + accruedInterest,
pool.totalDepositShares + feeShares,
Math.Rounding.Down
);
}
/// @notice Fetch debt owed by a position to a given pool
function getBorrowsOf(uint256 poolId, address position) public view returns (uint256) {
PoolData storage pool = poolDataFor[poolId];
(uint256 accruedInterest,) = simulateAccrue(pool);
// [ROUND] round up to enable enable complete debt repayment
return _convertToAssets(
borrowSharesOf[poolId][position],
pool.totalBorrowAssets + accruedInterest,
pool.totalBorrowShares,
Math.Rounding.Up
);
}
/// @notice Fetch the total amount of assets currently deposited in a pool
function getTotalAssets(uint256 poolId) public view returns (uint256) {
PoolData storage pool = poolDataFor[poolId];
(uint256 accruedInterest,) = simulateAccrue(pool);
return pool.totalDepositAssets + accruedInterest;
}
/// @notice Fetch total amount of debt owed to a given pool id
function getTotalBorrows(uint256 poolId) public view returns (uint256) {
PoolData storage pool = poolDataFor[poolId];
(uint256 accruedInterest,) = simulateAccrue(pool);
return pool.totalBorrowAssets + accruedInterest;
}
/// @notice Fetch current rate model for a given pool id
function getRateModelFor(uint256 poolId) public view returns (address rateModel) {
return poolDataFor[poolId].rateModel;
}
/// @notice Fetch pool cap for a given pool
function getPoolCapFor(uint256 poolId) public view returns (uint256) {
return poolDataFor[poolId].depositCap;
}
/// @notice Fetch borrow cap for a given pool
function getBorrowCapFor(uint256 poolId) public view returns (uint256) {
return poolDataFor[poolId].borrowCap;
}
/// @notice Fetch the debt asset address for a given pool
function getPoolAssetFor(uint256 poolId) public view returns (address) {
return poolDataFor[poolId].asset;
}
/// @notice Fetch equivalent shares amount for given assets
function convertToShares(
uint256 assets,
uint256 totalAssets,
uint256 totalShares
)
external
pure
returns (uint256 shares)
{
shares = _convertToShares(assets, totalAssets, totalShares, Math.Rounding.Down);
}
function _convertToShares(
uint256 assets,
uint256 totalAssets,
uint256 totalShares,
Math.Rounding rounding
)
internal
pure
returns (uint256 shares)
{
if (totalAssets == 0) return assets;
shares = assets.mulDiv(totalShares, totalAssets, rounding);
}
/// @notice Fetch equivalent asset amount for given shares
function convertToAssets(
uint256 shares,
uint256 totalAssets,
uint256 totalShares
)
external
pure
returns (uint256 assets)
{
assets = _convertToAssets(shares, totalAssets, totalShares, Math.Rounding.Down);
}
function _convertToAssets(
uint256 shares,
uint256 totalAssets,
uint256 totalShares,
Math.Rounding rounding
)
internal
pure
returns (uint256 assets)
{
if (totalShares == 0) return shares;
assets = shares.mulDiv(totalAssets, totalShares, rounding);
}
/// @notice Deposit assets to a pool
/// @param poolId Pool id
/// @param assets Amount of assets to be deposited
/// @param receiver Address to deposit assets on behalf of
/// @return shares Amount of pool deposit shares minted
function deposit(uint256 poolId, uint256 assets, address receiver) public whenNotPaused returns (uint256 shares) {
PoolData storage pool = poolDataFor[poolId];
if (pool.isPaused) revert Pool_PoolPaused(poolId);
if (pool.totalDepositAssets == 0 && pool.totalDepositShares != 0) revert Pool_ZeroAssetsNonZeroShares(poolId);
// update state to accrue interest since the last time accrue() was called
accrue(pool, poolId);
// Need to transfer before or ERC777s could reenter, or bypass the pool cap
IERC20(pool.asset).safeTransferFrom(msg.sender, address(this), assets);
shares = _convertToShares(assets, pool.totalDepositAssets, pool.totalDepositShares, Math.Rounding.Down);
if (shares == 0) revert Pool_ZeroSharesDeposit(poolId, assets);
pool.totalDepositAssets += assets;
pool.totalDepositShares += shares;
if (pool.totalDepositAssets > pool.depositCap) revert Pool_PoolCapExceeded(poolId);
if (pool.totalDepositShares > MAX_DEPOSIT_SHARES) revert Pool_MaxDepositShares(poolId);
_mint(receiver, poolId, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/// @notice Withdraw assets from a pool
/// @param poolId Pool id
/// @param assets Amount of assets to be redeemed
/// @param receiver Address that receives redeemed assets
/// @param owner Address to redeem on behalf of
/// @return shares Amount of shares redeemed from the pool
function withdraw(
uint256 poolId,
uint256 assets,
address receiver,
address owner
)
public
whenNotPaused
returns (uint256 shares)
{
PoolData storage pool = poolDataFor[poolId];
if (pool.isPaused) revert Pool_PoolPaused(poolId);
// update state to accrue interest since the last time accrue() was called
accrue(pool, poolId);
shares = _convertToShares(assets, pool.totalDepositAssets, pool.totalDepositShares, Math.Rounding.Up);
// check for rounding error since convertToShares rounds down
if (shares == 0) revert Pool_ZeroShareWithdraw(poolId, assets);
if (msg.sender != owner && !isOperator[owner][msg.sender]) {
uint256 allowed = allowance[owner][msg.sender][poolId];
if (allowed != type(uint256).max) allowance[owner][msg.sender][poolId] = allowed - shares;
}
uint256 maxWithdrawAssets = pool.totalDepositAssets - pool.totalBorrowAssets;
uint256 totalBalance = IERC20(pool.asset).balanceOf(address(this));
maxWithdrawAssets = (totalBalance > maxWithdrawAssets) ? maxWithdrawAssets : totalBalance;
if (maxWithdrawAssets < assets) revert Pool_InsufficientWithdrawLiquidity(poolId, maxWithdrawAssets, assets);
pool.totalDepositAssets -= assets;
pool.totalDepositShares -= shares;
_burn(owner, poolId, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
IERC20(pool.asset).safeTransfer(receiver, assets);
}
/// @notice Accrue interest and fees for a given pool
function accrue(uint256 id) external {
PoolData storage pool = poolDataFor[id];
accrue(pool, id);
}
function simulateAccrue(PoolData storage pool) internal view returns (uint256, uint256) {
if (block.timestamp == pool.lastUpdated) return (0, 0);
uint256 interestAccrued = IRateModel(pool.rateModel).getInterestAccrued(
pool.lastUpdated, pool.totalBorrowAssets, pool.totalDepositAssets
);
uint256 interestFee = pool.interestFee;
if (interestFee == 0) return (interestAccrued, 0);
// [ROUND] floor fees in favor of pool lenders
uint256 feeAssets = interestAccrued.mulDiv(pool.interestFee, 1e18);
// [ROUND] round down in favor of pool lenders
uint256 feeShares = _convertToShares(
feeAssets,
pool.totalDepositAssets + interestAccrued - feeAssets,
pool.totalDepositShares,
Math.Rounding.Down
);
return (interestAccrued, feeShares);
}
/// @dev update pool state to accrue interest since the last time accrue() was called
function accrue(PoolData storage pool, uint256 id) internal {
(uint256 interestAccrued, uint256 feeShares) = simulateAccrue(pool);
if (feeShares != 0) _mint(feeRecipient, id, feeShares);
// update pool state
pool.totalDepositShares += feeShares;
pool.totalBorrowAssets += interestAccrued;
pool.totalDepositAssets += interestAccrued;
// store a timestamp for this accrue() call
// used to compute the pending interest next time accrue() is called
pool.lastUpdated = block.timestamp;
}
/// @notice Mint borrow shares and send borrowed assets to the borrowing position
/// @param position the position to mint shares to
/// @param amt the amount of assets to borrow, denominated in notional asset units
/// @return borrowShares the amount of shares minted
function borrow(
uint256 poolId,
address position,
uint256 amt
)
external
whenNotPaused
returns (uint256 borrowShares)
{
PoolData storage pool = poolDataFor[poolId];
if (pool.isPaused) revert Pool_PoolPaused(poolId);
// revert if the caller is not the position manager
if (msg.sender != positionManager) revert Pool_OnlyPositionManager(poolId, msg.sender);
// revert if borrow amount is too low
if (RiskEngine(riskEngine).getValueInEth(pool.asset, amt) < minBorrow) {
revert Pool_BorrowAmountTooLow(poolId, pool.asset, amt);
}
// update state to accrue interest since the last time accrue() was called
accrue(pool, poolId);
// pools cannot share liquidity among themselves, revert if borrow amt exceeds pool liquidity
uint256 assetsInPool = pool.totalDepositAssets - pool.totalBorrowAssets;
if (assetsInPool < amt) revert Pool_InsufficientBorrowLiquidity(poolId, assetsInPool, amt);
// compute borrow shares equivalant for notional borrow amt
// [ROUND] round up shares minted, to ensure they capture the borrowed amount
borrowShares = _convertToShares(amt, pool.totalBorrowAssets, pool.totalBorrowShares, Math.Rounding.Up);
// revert if borrow amt is too small
if (borrowShares == 0) revert Pool_ZeroSharesBorrow(poolId, amt);
// check that final debt amount is greater than min debt
uint256 newBorrowAssets = _convertToAssets(
borrowSharesOf[poolId][position] + borrowShares,
pool.totalBorrowAssets + amt,
pool.totalBorrowShares + borrowShares,
Math.Rounding.Down
);
if (RiskEngine(riskEngine).getValueInEth(pool.asset, newBorrowAssets) < minDebt) {
revert Pool_DebtTooLow(poolId, pool.asset, newBorrowAssets);
}
// update total pool debt, denominated in notional asset units and shares
pool.totalBorrowAssets += amt;
pool.totalBorrowShares += borrowShares;
borrowSharesOf[poolId][position] += borrowShares;
// total borrow shares and total borrow assets checks
if (pool.totalBorrowAssets > pool.borrowCap) revert Pool_BorrowCapExceeded(poolId);
if (pool.totalBorrowShares > MAX_BORROW_SHARES) revert Pool_MaxBorrowShares(poolId);
// compute origination fee amt
// [ROUND] origination fee is rounded down, in favor of the borrower
uint256 fee = amt.mulDiv(pool.originationFee, 1e18);
address asset = pool.asset;
// send origination fee to owner
if (fee > 0) IERC20(asset).safeTransfer(feeRecipient, fee);
// send borrowed assets to position
IERC20(asset).safeTransfer(position, amt - fee);
emit Borrow(position, poolId, asset, amt);
}
/// @notice Decrease position debt via repayment of debt and burn borrow shares
/// @dev Assumes assets have already been sent to the pool
/// @param position the position for which debt is being repaid
/// @param amt the notional amount of debt asset repaid
/// @return remainingShares remaining debt in borrow shares owed by the position
function repay(
uint256 poolId,
address position,
uint256 amt
)
external
whenNotPaused
returns (uint256 remainingShares)
{
PoolData storage pool = poolDataFor[poolId];
// the only way to call repay() is through the position manager
// PositionManager.repay() MUST transfer the assets to be repaid before calling Pool.repay()
// this function assumes the transfer of assets was completed successfully
// there is an implicit assumption that assets were transferred in the same txn lest
// the call to Pool.repay() is not frontrun allowing debt repayment for another position
// revert if the caller is not the position manager
if (msg.sender != positionManager) revert Pool_OnlyPositionManager(poolId, msg.sender);
// update state to accrue interest since the last time accrue() was called
accrue(pool, poolId);
// compute borrow shares equivalent to notional asset amt
// [ROUND] burn fewer borrow shares, to ensure excess debt isn't pushed to others
uint256 borrowShares = _convertToShares(amt, pool.totalBorrowAssets, pool.totalBorrowShares, Math.Rounding.Down);
// revert if repaid amt is too small
if (borrowShares == 0) revert Pool_ZeroSharesRepay(poolId, amt);
// check that final debt amount is greater than min debt
remainingShares = borrowSharesOf[poolId][position] - borrowShares;
if (remainingShares > 0) {
uint256 newBorrowAssets = _convertToAssets(
remainingShares, pool.totalBorrowAssets - amt, pool.totalBorrowShares - borrowShares, Math.Rounding.Down
);
if (RiskEngine(riskEngine).getValueInEth(pool.asset, newBorrowAssets) < minDebt) {
revert Pool_DebtTooLow(poolId, pool.asset, newBorrowAssets);
}
}
// update total pool debt, denominated in notional asset units, and shares
pool.totalBorrowAssets -= amt;
pool.totalBorrowShares -= borrowShares;
// update and return remaining position debt, denominated in borrow shares
borrowSharesOf[poolId][position] = remainingShares;
emit Repay(position, poolId, pool.asset, amt);
return remainingShares;
}
function rebalanceBadDebt(uint256 poolId, address position) external whenNotPaused {
PoolData storage pool = poolDataFor[poolId];
accrue(pool, poolId);
// revert if the caller is not the position manager
if (msg.sender != positionManager) revert Pool_OnlyPositionManager(poolId, msg.sender);
// compute pool and position debt in shares and assets
uint256 totalBorrowShares = pool.totalBorrowShares;
uint256 totalBorrowAssets = pool.totalBorrowAssets;
uint256 borrowShares = borrowSharesOf[poolId][position];
// [ROUND] round up against lenders
uint256 borrowAssets = _convertToAssets(borrowShares, totalBorrowAssets, totalBorrowShares, Math.Rounding.Up);
// rebalance bad debt across lenders
pool.totalBorrowShares = totalBorrowShares - borrowShares;
// handle borrowAssets being rounded up to be greater than totalBorrowAssets
pool.totalBorrowAssets = (totalBorrowAssets > borrowAssets) ? totalBorrowAssets - borrowAssets : 0;
uint256 totalDepositAssets = pool.totalDepositAssets;
pool.totalDepositAssets = (totalDepositAssets > borrowAssets) ? totalDepositAssets - borrowAssets : 0;
borrowSharesOf[poolId][position] = 0;
}
/// @notice Initialize a new pool
/// @param owner Pool owner
/// @param asset Pool debt asset
/// @param depositCap Pool asset cap
/// @param borrowCap Pool debt ceiling
/// @param rateModelKey Registry key for interest rate model
/// @return poolId Pool id for initialized pool
function initializePool(
address owner,
address asset,
bytes32 rateModelKey,
uint256 depositCap,
uint256 borrowCap,
uint256 initialDepositAmt
)
external
whenNotPaused
returns (uint256 poolId)
{
if (owner == address(0)) revert Pool_ZeroAddressOwner();
if (RiskEngine(riskEngine).oracleFor(asset) == address(0)) revert Pool_OracleNotFound(asset);
address rateModel = Registry(registry).rateModelFor(rateModelKey);
if (rateModel == address(0)) revert Pool_RateModelNotFound(rateModelKey);
poolId = uint256(keccak256(abi.encodePacked(owner, asset, rateModelKey)));
if (ownerOf[poolId] != address(0)) revert Pool_PoolAlreadyInitialized(poolId);
ownerOf[poolId] = owner;
PoolData memory poolData = PoolData({
isPaused: false,
asset: asset,
rateModel: rateModel,
borrowCap: borrowCap,
depositCap: depositCap,
lastUpdated: block.timestamp,
interestFee: defaultInterestFee,
originationFee: defaultOriginationFee,
totalBorrowAssets: 0,
totalBorrowShares: 0,
totalDepositAssets: 0,
totalDepositShares: 0
});
poolDataFor[poolId] = poolData;
// burn initial deposit, assume msg.sender has approved
uint256 shares = deposit(poolId, initialDepositAmt, DEAD_ADDRESS);
if (shares < MIN_BURNED_SHARES) revert Pool_MinBurnedShares(shares);
emit PoolInitialized(poolId, owner, asset);
emit RateModelUpdated(poolId, rateModel);
emit PoolCapSet(poolId, depositCap);
emit BorrowCapSet(poolId, borrowCap);
}
/// @notice Toggle paused state for a pool to restrict deposit and borrows
function togglePause(uint256 poolId) external {
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
PoolData storage pool = poolDataFor[poolId];
pool.isPaused = !pool.isPaused;
emit PoolPauseToggled(poolId, pool.isPaused);
}
/// @notice Pause all pools and functions
function togglePauseAll() external onlyOwner {
if (PausableUpgradeable.paused()) PausableUpgradeable._unpause();
else PausableUpgradeable._pause();
}
/// @notice Update pool asset cap to restrict total amount of assets deposited
function setPoolCap(uint256 poolId, uint256 depositCap) external {
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
poolDataFor[poolId].depositCap = depositCap;
emit PoolCapSet(poolId, depositCap);
}
/// @notice Update pool borrow cap to restrict total amount of assets borrowed
function setBorrowCap(uint256 poolId, uint256 borrowCap) external {
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
poolDataFor[poolId].borrowCap = borrowCap;
emit BorrowCapSet(poolId, borrowCap);
}
/// @notice Update base pool owner
function setPoolOwner(uint256 poolId, address newOwner) external {
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
// address(0) cannot own pools since it is used to denote uninitalized pools
if (newOwner == address(0)) revert Pool_ZeroAddressOwner();
ownerOf[poolId] = newOwner;
emit PoolOwnerSet(poolId, newOwner);
}
/// @notice Propose a interest rate model update for a pool
/// @dev overwrites any pending or expired updates
function requestRateModelUpdate(uint256 poolId, bytes32 rateModelKey) external {
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
// store the rateModel instead of the registry key to mitigate issues
// arising from registry changes taking place between request/accept
// to pull registry update, call this function with the same key again
address rateModel = Registry(registry).rateModelFor(rateModelKey);
if (rateModel == address(0)) revert Pool_RateModelNotFound(rateModelKey);
RateModelUpdate memory rateModelUpdate =
RateModelUpdate({ rateModel: rateModel, validAfter: block.timestamp + TIMELOCK_DURATION });
rateModelUpdateFor[poolId] = rateModelUpdate;
emit RateModelUpdateRequested(poolId, rateModel);
}
/// @notice Apply a pending interest rate model change for a pool
function acceptRateModelUpdate(uint256 poolId) external {
accrue(poolDataFor[poolId], poolId); // accrue pending interest using previous rate model
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
RateModelUpdate memory rateModelUpdate = rateModelUpdateFor[poolId];
// revert if there is no update to apply
if (rateModelUpdate.validAfter == 0) revert Pool_NoRateModelUpdate(poolId);
// revert if called before timelock delay has passed
if (block.timestamp < rateModelUpdate.validAfter) {
revert Pool_TimelockPending(poolId, block.timestamp, rateModelUpdate.validAfter);
}
// revert if timelock deadline has passed
if (block.timestamp > rateModelUpdate.validAfter + TIMELOCK_DEADLINE) {
revert Pool_TimelockExpired(poolId, block.timestamp, rateModelUpdate.validAfter);
}
// apply update
poolDataFor[poolId].rateModel = rateModelUpdate.rateModel;
delete rateModelUpdateFor[poolId];
emit RateModelUpdated(poolId, rateModelUpdate.rateModel);
}
/// @notice Reject pending interest rate model update
function rejectRateModelUpdate(uint256 poolId) external {
if (msg.sender != ownerOf[poolId]) revert Pool_OnlyPoolOwner(poolId, msg.sender);
emit RateModelUpdateRejected(poolId, rateModelUpdateFor[poolId].rateModel);
delete rateModelUpdateFor[poolId];
}
/// @notice Set protocol registry address
/// @param _registry Registry address
function setRegistry(address _registry) external onlyOwner {
registry = _registry;
updateFromRegistry();
emit RegistrySet(_registry);
}
/// @notice Set interest fee for given pool
/// @param poolId Pool id
/// @param interestFee New interest fee
function setInterestFee(uint256 poolId, uint256 interestFee) external onlyOwner {
if (interestFee > 1e18) revert Pool_FeeTooHigh();
PoolData storage pool = poolDataFor[poolId];
accrue(pool, poolId);
pool.interestFee = interestFee;
emit InterestFeeSet(poolId, interestFee);
}
/// @notice Set origination fee for given pool
/// @param poolId Pool id
/// @param originationFee New origination fee
function setOriginationFee(uint256 poolId, uint256 originationFee) external onlyOwner {
if (originationFee > 1e18) revert Pool_FeeTooHigh();
poolDataFor[poolId].originationFee = originationFee;
emit OriginationFeeSet(poolId, originationFee);
}
/// @notice Update the minimum borrow amount
function setMinBorrow(uint256 newMinBorrow) external onlyOwner {
minBorrow = newMinBorrow;
emit MinBorrowSet(newMinBorrow);
}
/// @notice Update the min debt amount
function setMinDebt(uint256 newMinDebt) external onlyOwner {
minDebt = newMinDebt;
emit MinDebtSet(newMinDebt);
}
function setFeeRecipient(address newFeeRecipient) external onlyOwner {
if (newFeeRecipient == address(0)) revert Pool_ZeroFeeRecipient();
feeRecipient = newFeeRecipient;
emit FeeRecipientSet(newFeeRecipient);
}
function setDefaultOriginationFee(uint256 newDefaultOriginationFee) external onlyOwner {
if (newDefaultOriginationFee > 1e18) revert Pool_FeeTooHigh();
defaultOriginationFee = newDefaultOriginationFee;
emit DefaultOriginationFeeSet(newDefaultOriginationFee);
}
function setDefaultInterestFee(uint256 newDefaultInterestFee) external onlyOwner {
if (newDefaultInterestFee > 1e18) revert Pool_FeeTooHigh();
defaultInterestFee = newDefaultInterestFee;
emit DefaultInterestFeeSet(newDefaultInterestFee);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { Pool } from "./Pool.sol";
import { RiskEngine } from "./RiskEngine.sol";
import { IterableSet } from "./lib/IterableSet.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title Position
contract Position {
using SafeERC20 for IERC20;
using IterableSet for IterableSet.AddressSet;
using IterableSet for IterableSet.Uint256Set;
/// @notice Position implementation version
uint256 public constant VERSION = 1;
/// @notice Maximum number of assets that a position can hold at once
uint256 public constant MAX_ASSETS = 5;
/// @notice Maximum number of pools that a position can borrow from at once
uint256 public constant MAX_DEBT_POOLS = 5;
/// @notice Sentiment Pool
Pool public immutable POOL;
/// @notice Sentiment Risk Engine
RiskEngine public immutable RISK_ENGINE;
/// @notice Sentiment Position Manager
address public immutable POSITION_MANAGER;
/// @dev Iterable uint256 set that stores pool ids with active borrows
IterableSet.Uint256Set internal debtPools;
/// @dev Iterable address set that stores assets held by the position
IterableSet.AddressSet internal positionAssets;
/// @notice Number of assets held by the position exceeds `MAX_ASSETS`
error Position_MaxAssetsExceeded(address position);
/// @notice Number of pools with active borrows exceeds `MAX_DEBT_POOLS`
error Position_MaxDebtPoolsExceeded(address position);
/// @notice Exec operation on the position returned false
error Position_ExecFailed(address position, address target);
/// @notice Function access restricted to Sentiment Position Manager
error Position_OnlyPositionManager(address position, address sender);
/// @notice Invalid base pool pair borrow
error Position_InvalidPoolPair(uint256 poolA, uint256 poolB);
/// @param pool Sentiment Singleton Pool
/// @param positionManager Sentiment Postion Manager
constructor(address pool, address positionManager, address riskEngine) {
POOL = Pool(pool);
POSITION_MANAGER = positionManager;
RISK_ENGINE = RiskEngine(riskEngine);
}
// positions can receive and hold ether to perform external operations.
// ether is otherwise ignored by the rest of the protocol. it does not count
// towards the position balance, pools cannot lend ether and it cannot be
// used as collateral to borrow other assets
receive() external payable { }
modifier onlyPositionManager() {
if (msg.sender != POSITION_MANAGER) revert Position_OnlyPositionManager(address(this), msg.sender);
_;
}
/// @notice Fetch list of pool ids with active borrows to the position
function getDebtPools() external view returns (uint256[] memory) {
return debtPools.getElements();
}
/// @notice Fetch list of assets currently held by the position
function getPositionAssets() external view returns (address[] memory) {
return positionAssets.getElements();
}
/// @notice Check if a given asset exists in the position asset set
/// @dev an asset with zero balance could be in the set until explicitly removed
function hasAsset(address asset) external view returns (bool) {
return positionAssets.contains(asset);
}
/// @notice Check if a given debt pool exists in the debt pool set
/// @dev Position.repay() removes the debt pool after complete repayment
function hasDebt(uint256 poolId) external view returns (bool) {
return debtPools.contains(poolId);
}
/// @notice Approve an external contract to spend funds from the position
/// @dev The position manager imposes additional checks that the spender is trusted
function approve(address token, address spender, uint256 amt) external onlyPositionManager {
// use forceApprove to handle tokens with non-standard return values
// and tokens that force setting allowance to zero before modification
IERC20(token).forceApprove(spender, amt);
}
/// @notice Transfer assets from a position to a given external address
/// @dev Any additional checks must be implemented in the position manager
function transfer(address to, address asset, uint256 amt) external onlyPositionManager {
// handle tokens with non-standard return values using safeTransfer
IERC20(asset).safeTransfer(to, amt);
}
/// @notice Intereact with external contracts using arbitrary calldata
/// @dev Target and calldata is validated by the position manager
function exec(address target, uint256 value, bytes calldata data) external onlyPositionManager {
(bool success,) = target.call{ value: value }(data);
if (!success) revert Position_ExecFailed(address(this), target);
}
/// @notice Add asset to the list of tokens currently held by the position
function addToken(address asset) external onlyPositionManager {
positionAssets.insert(asset);
if (positionAssets.length() > MAX_ASSETS) revert Position_MaxAssetsExceeded(address(this));
}
/// @notice Remove asset from the list of tokens currrently held by the position
function removeToken(address asset) external onlyPositionManager {
positionAssets.remove(asset);
}
/// @notice Signal that the position has borrowed from a given pool
/// @dev Position assumes that this is done after debt assets have been transferred and
/// Pool.borrow() has already been called
function borrow(uint256 poolId, uint256) external onlyPositionManager {
// check if existing debt pools allow co-borrowing with given pool
uint256[] memory pools = debtPools.getElements();
uint256 debtPoolsLen = debtPools.length();
for (uint256 i; i < debtPoolsLen; ++i) {
if (poolId == pools[i]) continue;
if (RISK_ENGINE.isAllowedPair(poolId, pools[i]) && RISK_ENGINE.isAllowedPair(pools[i], poolId)) continue;
revert Position_InvalidPoolPair(poolId, pools[i]);
}
// update debt pools set
debtPools.insert(poolId);
if (debtPools.length() > MAX_DEBT_POOLS) revert Position_MaxDebtPoolsExceeded(address(this));
}
/// @notice Signal that the position has repaid debt to a given pool
/// @dev Position assumes that this is done after debt assets have been transferred and
/// Pool.repay() has been called so the pool can be removed from `debtPools` as needed
function repay(uint256 poolId, uint256) external onlyPositionManager {
if (POOL.getBorrowsOf(poolId, address(this)) == 0) debtPools.remove(poolId);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// types
import { Pool } from "./Pool.sol";
import { Position } from "./Position.sol";
import { Registry } from "./Registry.sol";
import { RiskEngine } from "./RiskEngine.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// libraries
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
// contracts
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
/// @title DebtData
/// @notice Data struct for position debt to be repaid by the liquidator
struct DebtData {
// poolId address for debt to be repaid
uint256 poolId;
// amount of debt to be repaid by the liquidator
// position manager assumes that this amount has already been approved
uint256 amt;
}
/// @title AssetData
/// @notice Data struct for collateral assets to be received by the liquidator
struct AssetData {
// token address
address asset;
// amount of collateral to be received by liquidator
uint256 amt;
}
/// @title Operation
/// @notice Operation type definitions that can be applied to a position
/// @dev Every operation except NewPosition requires that the caller must be an authz caller or owner
enum Operation {
NewPosition, // create2 a new position with a given type, no auth needed
// the following operations require msg.sender to be authorized
Exec, // execute arbitrary calldata on a position
Deposit, // Add collateral to a given position
Transfer, // transfer assets from the position to a external address
Approve, // allow a spender to transfer assets from a position
Repay, // decrease position debt
Borrow, // increase position debt
AddToken, // upsert collateral asset to position storage
RemoveToken // remove collateral asset from position storage
}
/// @title Action
/// @notice Generic data struct to create a common data container for all operation types
/// @dev target and data are interpreted in different ways based on the operation type
struct Action {
// operation type
Operation op;
// dynamic bytes data, interepreted differently across operation types
bytes data;
}
/// @title PositionManager
/// @notice Handles the deployment and use of Positions against the Singleton Pool Contract
contract PositionManager is ReentrancyGuardUpgradeable, OwnableUpgradeable, PausableUpgradeable {
using Math for uint256;
using SafeERC20 for IERC20;
uint256 internal constant WAD = 1e18;
uint256 internal constant BAD_DEBT_LIQUIDATION_FEE = 0;
// keccak(SENTIMENT_POOL_KEY)
bytes32 public constant SENTIMENT_POOL_KEY = 0x1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c09728;
// keccak(SENTIMENT_RISK_ENGINE_KEY)
bytes32 public constant SENTIMENT_RISK_ENGINE_KEY =
0x5b6696788621a5d6b5e3b02a69896b9dd824ebf1631584f038a393c29b6d7555;
// keccak(SENTIMENT_POSITION_BEACON_KEY)
bytes32 public constant SENTIMENT_POSITION_BEACON_KEY =
0x6e7384c78b0e09fb848f35d00a7b14fc1ad10ae9b10117368146c0e09b6f2fa2;
/// @notice Sentiment Singleton Pool
Pool public pool;
/// @notice Sentiment Registry
Registry public registry;
/// @notice Sentiment Risk Engine
RiskEngine public riskEngine;
/// @notice Position Beacon
address public positionBeacon;
/// @notice Fetch owner for given position
mapping(address position => address owner) public ownerOf;
/// @notice Check if a given address is allowed to operate on a particular position
/// @dev [caller][position] => [isAuthorized] stores if caller is authorized to operate on position
mapping(address position => mapping(address caller => bool isAuthz)) public isAuth;
// universe mappings:
// the following two mappings define the universe of the protocol. this is used to define a
// subset of the network that a position can freely interact with. isKnownAddress defines
// recognized addresses. these include assets that a position interacts with and addresses that
// can approved as spenders' for assets in a position. isKnownFunc defines the exec universe
// for a function by creating a mapping of particular target-function pairs that can be called
// by a position.
/// @notice Check if a given address is recognized by the protocol
mapping(address asset => bool isAllowed) public isKnownAsset;
/// @notice Check if a given spender is recognized by the protocol
mapping(address spender => bool isKnown) public isKnownSpender;
/// @notice Check if a position can interact with a given target-function pair
mapping(address target => mapping(bytes4 method => bool isAllowed)) public isKnownFunc;
/// @notice Pool address was updated
event PoolSet(address pool);
/// @notice Position Beacon address was updated
event BeaconSet(address beacon);
/// @notice Protocol registry address was updated
event RegistrySet(address registry);
/// @notice Risk Engine address was updated
event RiskEngineSet(address riskEngine);
/// @notice Position authorization was toggled
event AuthToggled(address indexed position, address indexed user, bool isAuth);
/// @notice Known state of an address was toggled
event ToggleKnownAsset(address indexed asset, bool isAllowed);
/// @notice Known state of an address was toggled
event ToggleKnownSpender(address indexed spender, bool isAllowed);
/// @notice Token was added to a position's asset list
event AddToken(address indexed position, address indexed caller, address asset);
/// @notice Known state of a target-function pair was toggled
event ToggleKnownFunc(address indexed target, bytes4 indexed method, bool isAllowed);
/// @notice Token was removed from a position's asset list
event RemoveToken(address indexed position, address indexed caller, address asset);
/// @notice Position was successfully liquidated
event Liquidation(address indexed position, address indexed liquidator, address indexed owner);
/// @notice New position was deployed
event PositionDeployed(address indexed position, address indexed caller, address indexed owner);
/// @notice Assets were deposited to a position
event Deposit(address indexed position, address indexed depositor, address asset, uint256 amount);
/// @notice Debt was repaid from a position to a pool
event Repay(address indexed position, address indexed caller, uint256 indexed poolId, uint256 amount);
/// @notice Assets were borrowed from a pool to a position
event Borrow(address indexed position, address indexed caller, uint256 indexed poolId, uint256 amount);
/// @notice An external operation was executed on a position
event Exec(address indexed position, address indexed caller, address indexed target, bytes4 functionSelector);
/// @notice Assets were transferred out of a position
event Transfer(
address indexed position, address indexed caller, address indexed target, address asset, uint256 amount
);
/// @notice Approval was granted for assets belonging to a position
event Approve(
address indexed position, address indexed caller, address indexed spender, address asset, uint256 amount
);
/// @notice The pool a position is trying to borrow from does not exist
error PositionManager_UnknownPool(uint256 poolId);
/// @notice Unknown spenders cannot be granted approval to position assets
error PositionManager_UnknownSpender(address spender);
/// @notice Position health check failed
error PositionManager_HealthCheckFailed(address position);
/// @notice Attempt to add unrecognized asset to a position's asset list
error PositionManager_AddUnknownToken(address asset);
/// @notice Attempt to approve unknown asset
error PositionManager_ApproveUnknownAsset(address asset);
/// @notice Attempt to deposit unrecognized asset to position
error PositionManager_DepositUnknownAsset(address asset);
/// @notice Attempt to transfer unrecognized asset out of position
error PositionManager_TransferUnknownAsset(address asset);
/// @notice Cannot liquidate healthy position
error PositionManager_LiquidateHealthyPosition(address position);
/// @notice Function access restricted to position owner only
error PositionManager_OnlyPositionOwner(address position, address sender);
/// @notice Unknown target-function selector pair
error PositionManager_UnknownFuncSelector(address target, bytes4 selector);
/// @notice Function access restricted to authorozied addresses
error PositionManager_OnlyPositionAuthorized(address position, address sender);
/// @notice Predicted position address does not match with deployed address
error PositionManager_PredictedPositionMismatch(address position, address predicted);
constructor() {
_disableInitializers();
}
/// @notice Initializer for TransparentUpgradeableProxy
/// @param owner_ PositionManager Owner
/// @param registry_ Sentiment Registry
function initialize(address owner_, address registry_) public initializer {
ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
OwnableUpgradeable.__Ownable_init();
PausableUpgradeable.__Pausable_init();
_transferOwnership(owner_);
registry = Registry(registry_);
}
/// @notice Fetch and update module addreses from the registry
function updateFromRegistry() public {
pool = Pool(registry.addressFor(SENTIMENT_POOL_KEY));
riskEngine = RiskEngine(registry.addressFor(SENTIMENT_RISK_ENGINE_KEY));
positionBeacon = registry.addressFor(SENTIMENT_POSITION_BEACON_KEY);
emit PoolSet(address(pool));
emit RiskEngineSet(address(riskEngine));
emit BeaconSet(positionBeacon);
}
/// @notice Toggle pause state of the PositionManager
function togglePause() external onlyOwner {
if (PausableUpgradeable.paused()) PausableUpgradeable._unpause();
else PausableUpgradeable._pause();
}
/// @notice Authorize a caller other than the owner to operate on a position
function toggleAuth(address user, address position) external {
// only account owners are allowed to modify authorizations
// disables transitive auth operations
if (msg.sender != ownerOf[position]) revert PositionManager_OnlyPositionOwner(position, msg.sender);
// update authz status in storage
isAuth[position][user] = !isAuth[position][user];
emit AuthToggled(position, user, isAuth[position][user]);
}
/// @notice Process a single action on a given position
/// @param position Position address
/// @param action Action config
function process(address position, Action calldata action) external nonReentrant {
_process(position, action);
if (riskEngine.getPositionHealthFactor(position) < WAD) revert PositionManager_HealthCheckFailed(position);
}
/// @notice Procces a batch of actions on a given position
/// @dev only one position can be operated on in one txn, including creation
/// @param position Position address
/// @param actions List of actions to process
function processBatch(address position, Action[] calldata actions) external nonReentrant {
// loop over actions and process them sequentially based on operation
uint256 actionsLength = actions.length;
for (uint256 i; i < actionsLength; ++i) {
_process(position, actions[i]);
}
// after all the actions are processed, the position should be within risk thresholds
if (riskEngine.getPositionHealthFactor(position) < WAD) revert PositionManager_HealthCheckFailed(position);
}
function _process(address position, Action calldata action) internal {
if (action.op == Operation.NewPosition) {
newPosition(position, action.data);
return;
}
if (!isAuth[position][msg.sender]) revert PositionManager_OnlyPositionAuthorized(position, msg.sender);
if (action.op == Operation.Exec) exec(position, action.data);
else if (action.op == Operation.Transfer) transfer(position, action.data);
else if (action.op == Operation.Deposit) deposit(position, action.data);
else if (action.op == Operation.Approve) approve(position, action.data);
else if (action.op == Operation.Repay) repay(position, action.data);
else if (action.op == Operation.Borrow) borrow(position, action.data);
else if (action.op == Operation.AddToken) addToken(position, action.data);
else if (action.op == Operation.RemoveToken) removeToken(position, action.data);
}
/// @dev deterministically deploy a new beacon proxy representing a position
/// @dev the target field in the action is the new owner of the position
function newPosition(address predictedAddress, bytes calldata data) internal whenNotPaused {
// data -> abi.encodePacked(address, bytes32)
// owner -> [:20] owner to create the position on behalf of
// salt -> [20:52] create2 salt for position
address owner = address(bytes20(data[0:20]));
bytes32 salt = bytes32(data[20:52]);
// hash salt with owner to mitigate positions being frontrun
salt = keccak256(abi.encodePacked(owner, salt));
// create2 a new position as a beacon proxy
address position = address(new BeaconProxy{ salt: salt }(positionBeacon, ""));
// update position owner
ownerOf[position] = owner;
// owner is authzd by default
isAuth[position][owner] = true;
// revert if predicted position address does not match deployed address
if (position != predictedAddress) revert PositionManager_PredictedPositionMismatch(position, predictedAddress);
emit PositionDeployed(position, msg.sender, owner);
}
/// @dev Operate on a position by interaction with external contracts using arbitrary calldata
function exec(address position, bytes calldata data) internal whenNotPaused {
// exec data is encodePacked (address, uint256, bytes)
// target -> [0:20] contract address to be called by the position
// value -> [20:52] the ether amount to be sent with the call
// function selector -> [52:56] function selector to be called on the target
// calldata -> [52:] represents the calldata including the func selector
address target = address(bytes20(data[:20]));
uint256 value = uint256(bytes32(data[20:52]));
bytes4 funcSelector = bytes4(data[52:56]);
if (!isKnownFunc[target][funcSelector]) revert PositionManager_UnknownFuncSelector(target, funcSelector);
Position(payable(position)).exec(target, value, data[52:]);
emit Exec(position, msg.sender, target, funcSelector);
}
/// @dev Transfer assets out of a position
function transfer(address position, bytes calldata data) internal whenNotPaused {
// data -> abi.encodePacked(address, address, uint256)
// recipient -> [0:20] address that will receive the transferred tokens
// asset -> [20:40] address of token to be transferred
// amt -> [40:72] amount of asset to be transferred
address recipient = address(bytes20(data[0:20]));
address asset = address(bytes20(data[20:40]));
uint256 amt = uint256(bytes32(data[40:72]));
if (!isKnownAsset[asset]) revert PositionManager_TransferUnknownAsset(asset);
// if the passed amt is type(uint).max assume transfer of the entire balance
if (amt == type(uint256).max) amt = IERC20(asset).balanceOf(position);
Position(payable(position)).transfer(recipient, asset, amt);
emit Transfer(position, msg.sender, recipient, asset, amt);
}
/// @dev Deposit assets from msg.sender to a position
function deposit(address position, bytes calldata data) internal {
// data -> abi.encodePacked(address, uint256)
// asset -> [0:20] address of token to be deposited
// amt -> [20: 52] amount of asset to be deposited
address asset = address(bytes20(data[0:20]));
uint256 amt = uint256(bytes32(data[20:52]));
// mitigate unknown assets being locked in positions
if (!isKnownAsset[asset]) revert PositionManager_DepositUnknownAsset(asset);
IERC20(asset).safeTransferFrom(msg.sender, position, amt);
emit Deposit(position, msg.sender, asset, amt);
}
/// @dev Approve a spender to use assets from a position
function approve(address position, bytes calldata data) internal {
// data -> abi.encodePacked(address, address, uint256)
// spender -> [0:20] address to be approved
// asset -> [20:40] address of token to be approves
// amt -> [40:72] amount of asset to be approved
address spender = address(bytes20(data[0:20]));
address asset = address(bytes20(data[20:40]));
uint256 amt = uint256(bytes32(data[40:72]));
if (!isKnownAsset[asset]) revert PositionManager_ApproveUnknownAsset(asset);
if (!isKnownSpender[spender]) revert PositionManager_UnknownSpender(spender);
// if the passed amt is type(uint).max assume approval of the entire balance
if (amt == type(uint256).max) amt = IERC20(asset).balanceOf(position);
Position(payable(position)).approve(asset, spender, amt);
emit Approve(position, msg.sender, spender, asset, amt);
}
/// @dev Decrease position debt via repayment. To repay the entire debt set `_amt` to uint.max
function repay(address position, bytes calldata data) internal {
// data -> abi.encodePacked(uint256, uint256)
// poolId -> [0:32] pool that recieves the repaid debt
// amt -> [32: 64] notional amount to be repaid
uint256 poolId = uint256(bytes32(data[0:32]));
uint256 amt = uint256(bytes32(data[32:64]));
// if the passed amt is type(uint).max assume repayment of the entire debt
if (amt == type(uint256).max) amt = pool.getBorrowsOf(poolId, position);
// transfer assets to be repaid from the position to the given pool
Position(payable(position)).transfer(address(pool), pool.getPoolAssetFor(poolId), amt);
// trigger pool repayment which assumes successful transfer of repaid assets
pool.repay(poolId, position, amt);
// signals repayment to the position and removes the debt pool if completely paid off
// any checks needed to validate repayment must be implemented in the position
Position(payable(position)).repay(poolId, amt);
emit Repay(position, msg.sender, poolId, amt);
}
/// @dev Increase position debt via borrowing
function borrow(address position, bytes calldata data) internal whenNotPaused {
// data -> abi.encodePacked(uint256, uint256)
// poolId -> [0:32] pool to borrow from
// amt -> [32:64] notional amount to be borrowed
uint256 poolId = uint256(bytes32(data[0:32]));
uint256 amt = uint256(bytes32(data[32:64]));
// revert if the given pool does not exist
if (pool.ownerOf(poolId) == address(0)) revert PositionManager_UnknownPool(poolId);
// transfer borrowed assets from given pool to position
// trigger pool borrow and increase debt owed by the position
pool.borrow(poolId, position, amt);
// signals a borrow operation without any actual transfer of borrowed assets
// any checks needed to validate the borrow must be implemented in the position
Position(payable(position)).borrow(poolId, amt);
emit Borrow(position, msg.sender, poolId, amt);
}
/// @dev Add a token address to the set of position assets
function addToken(address position, bytes calldata data) internal {
// data -> abi.encodePacked(address)
// asset -> [0:20] address of asset to be registered as collateral
address asset = address(bytes20(data[0:20]));
// avoid interactions with unknown assets
if (!isKnownAsset[asset]) revert PositionManager_AddUnknownToken(asset);
Position(payable(position)).addToken(asset); // validation should be in the position contract
emit AddToken(position, msg.sender, asset);
}
/// @dev Remove a token address from the set of position assets
function removeToken(address position, bytes calldata data) internal whenNotPaused {
// data -> abi.encodePacked(address)
// asset -> address of asset to be deregistered as collateral
address asset = address(bytes20(data[0:20]));
Position(payable(position)).removeToken(asset);
emit RemoveToken(position, msg.sender, asset);
}
/// @notice Liquidate an unhealthy position
/// @param position Position address
/// @param debtData DebtData object for debts to be repaid
/// @param assetData AssetData object for assets to be seized
/// @dev DebtData must be sorted by poolId, AssetData must be sorted by asset (ascending)
function liquidate(
address position,
DebtData[] calldata debtData,
AssetData[] calldata assetData
)
external
nonReentrant
{
(uint256 prevHealthFactor, uint256 liqFee, DebtData[] memory repayData, AssetData[] memory seizeData) =
riskEngine.validateLiquidation(position, debtData, assetData);
// liquidate
_transferAssetsToLiquidator(position, liqFee, seizeData);
_repayPositionDebt(position, repayData);
// verify that position health improves
uint256 healthFactor = riskEngine.getPositionHealthFactor(position);
if (healthFactor <= prevHealthFactor) revert PositionManager_HealthCheckFailed(position);
emit Liquidation(position, msg.sender, ownerOf[position]);
}
/// @notice Liquidate a position with bad debt
/// @dev Bad debt positions cannot be liquidated partially
function liquidateBadDebt(address position, DebtData[] calldata debtData) external nonReentrant {
(DebtData[] memory repayData, AssetData[] memory seizeData) =
riskEngine.validateBadDebtLiquidation(position, debtData);
// liquidator repays some of the bad debt, and receives all of the position assets
_transferAssetsToLiquidator(position, BAD_DEBT_LIQUIDATION_FEE, seizeData); // zero protocol fee
_repayPositionDebt(position, repayData);
// settle remaining bad debt for the given position
uint256[] memory debtPools = Position(payable(position)).getDebtPools();
uint256 debtPoolsLength = debtPools.length;
for (uint256 i; i < debtPoolsLength; ++i) {
pool.rebalanceBadDebt(debtPools[i], position);
Position(payable(position)).repay(debtPools[i], type(uint256).max);
}
}
function _transferAssetsToLiquidator(
address position,
uint256 liquidationFee,
AssetData[] memory assetData
)
internal
{
// transfer position assets to the liquidator and accrue protocol liquidation fees
uint256 assetDataLength = assetData.length;
for (uint256 i; i < assetDataLength; ++i) {
uint256 feeAssets;
if (liquidationFee > 0) {
feeAssets = liquidationFee.mulDiv(assetData[i].amt, WAD); // compute fee assets
Position(payable(position)).transfer(owner(), assetData[i].asset, feeAssets); // transfer fee assets
}
// transfer assets to the liquidator
Position(payable(position)).transfer(msg.sender, assetData[i].asset, assetData[i].amt - feeAssets);
}
}
function _repayPositionDebt(address position, DebtData[] memory debtData) internal {
// sequentially repay position debts
// assumes the position manager is approved to pull assets from the liquidator
uint256 debtDataLength = debtData.length;
for (uint256 i; i < debtDataLength; ++i) {
uint256 poolId = debtData[i].poolId;
address poolAsset = pool.getPoolAssetFor(poolId);
uint256 amt = debtData[i].amt;
// transfer debt asset from the liquidator to the pool
IERC20(poolAsset).safeTransferFrom(msg.sender, address(pool), amt);
// trigger pool repayment which assumes successful transfer of repaid assets
pool.repay(poolId, position, amt);
// update position to reflect repayment of debt by liquidator
Position(payable(position)).repay(poolId, amt);
}
}
/// @notice Set the protocol registry address
function setRegistry(address _registry) external onlyOwner {
registry = Registry(_registry);
updateFromRegistry();
emit RegistrySet(_registry);
}
/// @notice Toggle asset inclusion in the known asset universe
function toggleKnownAsset(address asset) external onlyOwner {
isKnownAsset[asset] = !isKnownAsset[asset];
emit ToggleKnownAsset(asset, isKnownAsset[asset]);
}
/// @notice Toggle spender inclusion in the known spender universe
function toggleKnownSpender(address spender) external onlyOwner {
isKnownSpender[spender] = !isKnownSpender[spender];
emit ToggleKnownSpender(spender, isKnownSpender[spender]);
}
/// @notice Toggle target-function pair inclusion in the known function universe
function toggleKnownFunc(address target, bytes4 method) external onlyOwner {
isKnownFunc[target][method] = !isKnownFunc[target][method];
emit ToggleKnownFunc(target, method, isKnownFunc[target][method]);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*//////////////////////////////////////////////////////////////
Registry
//////////////////////////////////////////////////////////////*/
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/// @title Registry
contract Registry is Ownable {
/// @notice Registry address for a given key hash updated
event AddressSet(bytes32 indexed key, address addr);
/// @notice Rate model address for a given key hash updated
event RateModelSet(bytes32 indexed key, address addr);
/// @notice Fetch module address for a given key hash
mapping(bytes32 key => address addr) public addressFor;
/// @notice Fetch rate model address for a given key hash
mapping(bytes32 key => address rateModel) public rateModelFor;
constructor() Ownable() { }
/// @notice Update module address for a given key hash
/// @param key Registry key hash
/// @param addr Updated module address for the key hash
function setAddress(bytes32 key, address addr) external onlyOwner {
addressFor[key] = addr;
emit AddressSet(key, addr);
}
/// @notice Update rate model address for a given key hash
/// @param key Registry key hash
/// @param rateModel Updated rate model address for the key hash
function setRateModel(bytes32 key, address rateModel) external onlyOwner {
rateModelFor[key] = rateModel;
emit RateModelSet(key, rateModel);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// types
import { Pool } from "./Pool.sol";
import { Position } from "./Position.sol";
import { AssetData, DebtData } from "./PositionManager.sol";
import { Registry } from "./Registry.sol";
import { RiskEngine } from "./RiskEngine.sol";
import { IOracle } from "./interfaces/IOracle.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// libraries
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
/// @title RiskModule
contract RiskModule {
using Math for uint256;
uint256 internal constant WAD = 1e18;
/// @notice Sentiment Registry Pool registry key hash
/// @dev keccak(SENTIMENT_POOL_KEY)
bytes32 public constant SENTIMENT_POOL_KEY = 0x1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c09728;
/// @notice Sentiment Risk Engine registry key hash
/// @dev keccak(SENTIMENT_RISK_ENGINE_KEY)
bytes32 public constant SENTIMENT_RISK_ENGINE_KEY =
0x5b6696788621a5d6b5e3b02a69896b9dd824ebf1631584f038a393c29b6d7555;
/// @notice Protocol liquidation fee, out of 1e18
uint256 public immutable LIQUIDATION_FEE;
/// @notice The discount on assets when liquidating, out of 1e18
uint256 public immutable LIQUIDATION_DISCOUNT;
/// @notice The updateable registry as a part of the 2step initialization process
Registry public immutable REGISTRY;
/// @notice Sentiment Singleton Pool
Pool public pool;
/// @notice Sentiment Risk Engine
RiskEngine public riskEngine;
/// @notice Pool address was updated
event PoolSet(address pool);
/// @notice Risk Engine address was updated
event RiskEngineSet(address riskEngine);
/// @notice Value of assets seized by the liquidator exceeds liquidation discount
error RiskModule_SeizedTooMuch(uint256 seizedValue, uint256 maxSeizedValue);
/// @notice Position contains an asset that is not supported by a pool that it borrows from
error RiskModule_UnsupportedAsset(address position, uint256 poolId, address asset);
/// @notice Minimum assets required in a position with non-zero debt cannot be zero
error RiskModule_ZeroMinReqAssets();
/// @notice Cannot liquidate healthy positions
error RiskModule_LiquidateHealthyPosition(address position);
/// @notice Position does not have any bad debt
error RiskModule_NoBadDebt(address position);
/// @notice Seized asset does not belong to to the position's asset list
error RiskModule_SeizeInvalidAsset(address position, address asset);
/// @notice Liquidation DebtData is invalid
error RiskModule_InvalidDebtData(uint256 poolId);
/// @notice Liquidation AssetData is invalid
error RiskModule_InvalidAssetData(address asset);
/// @notice Constructor for Risk Module, which should be registered with the RiskEngine
/// @param registry_ The address of the registry contract
/// @param liquidationDiscount_ The discount on assets when liquidating, out of 1e18
constructor(address registry_, uint256 liquidationDiscount_, uint256 liquidationFee_) {
REGISTRY = Registry(registry_);
LIQUIDATION_DISCOUNT = liquidationDiscount_;
LIQUIDATION_FEE = liquidationFee_;
}
/// @notice Updates the pool and risk engine from the registry
function updateFromRegistry() external {
pool = Pool(REGISTRY.addressFor(SENTIMENT_POOL_KEY));
riskEngine = RiskEngine(REGISTRY.addressFor(SENTIMENT_RISK_ENGINE_KEY));
emit PoolSet(address(pool));
emit RiskEngineSet(address(riskEngine));
}
/// @notice Fetch position health factor
function getPositionHealthFactor(address position) public view returns (uint256) {
// a position can have multiple states:
// 1. (zero debt, zero assets) -> max health
// 2. (zero debt, non-zero assets) -> max health
// 3. (non-zero debt, zero assets) -> invalid state, zero health
// 4. (non-zero debt, non-zero assets) AND (debt > assets) -> bad debt, zero health
// 5. (non-zero debt, non-zero assets) AND (assets >= debt) -> determined by weighted ltv
(uint256 totalAssets, uint256 totalDebt, uint256 weightedLtv) = getRiskData(position);
if (totalDebt == 0) return type(uint256).max; // (zero debt, zero assets) AND (zero debt, non-zero assets)
if (totalDebt > totalAssets) return 0; // (non-zero debt, zero assets) AND bad debt
return weightedLtv.mulDiv(totalAssets, totalDebt); // (non-zero debt, non-zero assets) AND no bad debt
}
/// @notice Fetch risk data for a position - total assets and debt in ETH, and its weighted LTV
/// @dev weightedLtv is zero if either total assets or total debt is zero
function getRiskData(address position) public view returns (uint256, uint256, uint256) {
(uint256 totalDebt, uint256[] memory debtPools, uint256[] memory debtValue) = getDebtData(position);
(uint256 totalAssets, address[] memory positionAssets, uint256[] memory assetValue) = getAssetData(position);
uint256 weightedLtv =
_getWeightedLtv(position, totalDebt, debtPools, debtValue, totalAssets, positionAssets, assetValue);
return (totalAssets, totalDebt, weightedLtv);
}
/// @notice Fetch debt data for position - total debt in ETH, active debt pools, and debt for each pool in ETH
function getDebtData(address position) public view returns (uint256, uint256[] memory, uint256[] memory) {
uint256 totalDebt;
uint256[] memory debtPools = Position(payable(position)).getDebtPools();
uint256[] memory debtValue = new uint256[](debtPools.length);
uint256 debtPoolsLength = debtPools.length;
for (uint256 i; i < debtPoolsLength; ++i) {
address poolAsset = pool.getPoolAssetFor(debtPools[i]);
uint256 borrowAmt = pool.getBorrowsOf(debtPools[i], position);
uint256 debtInEth = riskEngine.getValueInEth(poolAsset, borrowAmt);
debtValue[i] = debtInEth;
totalDebt += debtInEth;
}
return (totalDebt, debtPools, debtValue);
}
/// @notice Fetch asset data for a position - total assets in ETH, position assets, and value of each asset in ETH
function getAssetData(address position) public view returns (uint256, address[] memory, uint256[] memory) {
uint256 totalAssets;
address[] memory positionAssets = Position(payable(position)).getPositionAssets();
uint256 positionAssetsLength = positionAssets.length;
uint256[] memory assetValue = new uint256[](positionAssetsLength);
for (uint256 i; i < positionAssetsLength; ++i) {
uint256 amt = IERC20(positionAssets[i]).balanceOf(position);
uint256 assetsInEth = riskEngine.getValueInEth(positionAssets[i], amt);
assetValue[i] = assetsInEth;
totalAssets += assetsInEth;
}
return (totalAssets, positionAssets, assetValue);
}
/// @notice Fetch weighted Ltv for a position
function getWeightedLtv(address position) public view returns (uint256) {
(uint256 totalDebt, uint256[] memory debtPools, uint256[] memory debtValue) = getDebtData(position);
(uint256 totalAssets, address[] memory positionAssets, uint256[] memory assetValue) = getAssetData(position);
return _getWeightedLtv(position, totalDebt, debtPools, debtValue, totalAssets, positionAssets, assetValue);
}
function _getWeightedLtv(
address position,
uint256 totalDebt,
uint256[] memory debtPools,
uint256[] memory debtValue,
uint256 totalAssets,
address[] memory positionAssets,
uint256[] memory assetValue
)
internal
view
returns (uint256 weightedLtv)
{
// handle empty, zero-debt, bad debt, and invalid position states
if (totalDebt == 0 || totalAssets == 0 || totalDebt > totalAssets) return 0;
uint256 debtPoolsLen = debtPools.length;
uint256 positionAssetsLen = positionAssets.length;
// O(debtPools.length * positionAssets.length)
for (uint256 i; i < debtPoolsLen; ++i) {
for (uint256 j; j < positionAssetsLen; ++j) {
uint256 ltv = riskEngine.ltvFor(debtPools[i], positionAssets[j]);
// every position asset must have a non-zero ltv in every debt pool
if (ltv == 0) revert RiskModule_UnsupportedAsset(position, debtPools[i], positionAssets[j]);
// ltv is weighted over two dimensions - proportion of debt value owed to a pool as a share of the
// total position debt and proportion of position asset value as a share of total position value
weightedLtv += debtValue[i].mulDiv(assetValue[j], WAD).mulDiv(ltv, WAD);
}
}
weightedLtv = weightedLtv.mulDiv(WAD, totalAssets).mulDiv(WAD, totalDebt);
}
/// @notice Used to validate liquidator data and value of assets seized
/// @param position Position being liquidated
/// @param debtData The debt data for the position
/// @param assetData The asset data for the position
function validateLiquidation(
address position,
DebtData[] calldata debtData,
AssetData[] calldata assetData
)
external
view
returns (uint256, uint256, DebtData[] memory, AssetData[] memory)
{
// ensure position is unhealthy
uint256 healthFactor = getPositionHealthFactor(position);
if (healthFactor >= WAD) revert RiskModule_LiquidateHealthyPosition(position);
// parse data for repayment and seizure
(uint256 totalRepayValue, DebtData[] memory repayData) = _getRepayData(position, debtData);
(uint256 totalSeizeValue, AssetData[] memory seizeData) = _getSeizeData(position, assetData);
// verify liquidator does not seize too much
uint256 maxSeizeValue = totalRepayValue.mulDiv(1e18, (1e18 - LIQUIDATION_DISCOUNT));
if (totalSeizeValue > maxSeizeValue) revert RiskModule_SeizedTooMuch(totalSeizeValue, maxSeizeValue);
// compute protocol liquidation fee as a portion of liquidator profit, if any
uint256 liqFee;
if (totalSeizeValue > totalRepayValue) {
liqFee = (totalSeizeValue - totalRepayValue).mulDiv(LIQUIDATION_FEE, totalSeizeValue);
}
return (healthFactor, liqFee, repayData, seizeData);
}
/// @notice validate bad debt liquidation call
/// @dev Positions with bad debt cannot be partially liquidated
function validateBadDebtLiquidation(
address position,
DebtData[] calldata debtData
)
external
view
returns (DebtData[] memory, AssetData[] memory)
{
// verify position has bad debt
(uint256 totalAssetValue, uint256 totalDebtValue,) = getRiskData(position);
if (totalAssetValue >= totalDebtValue) revert RiskModule_NoBadDebt(position);
// parse repayment data
(uint256 totalRepayValue, DebtData[] memory repayData) = _getRepayData(position, debtData);
// verify that liquidator repays enough to seize all position assets
uint256 maxSeizeValue = totalRepayValue.mulDiv(1e18, (1e18 - LIQUIDATION_DISCOUNT));
if (totalAssetValue > maxSeizeValue) revert RiskModule_SeizedTooMuch(totalAssetValue, maxSeizeValue);
// generate asset seizure data - since bad debt liquidations are not partial, all assets are seized
AssetData[] memory seizeData = _getBadDebtSeizeData(position);
return (repayData, seizeData);
}
function _getRepayData(
address position,
DebtData[] calldata debtData
)
internal
view
returns (uint256 totalRepayValue, DebtData[] memory repayData)
{
_validateDebtData(position, debtData);
uint256 debtDataLen = debtData.length;
repayData = debtData; // copy debtData and replace all type(uint).max with repay amounts
for (uint256 i; i < debtDataLen; ++i) {
uint256 poolId = repayData[i].poolId;
uint256 repayAmt = repayData[i].amt;
if (repayAmt == type(uint256).max) {
repayAmt = pool.getBorrowsOf(poolId, position);
repayData[i].amt = repayAmt;
}
totalRepayValue += riskEngine.getValueInEth(pool.getPoolAssetFor(poolId), repayAmt);
}
}
function _getSeizeData(
address position,
AssetData[] calldata assetData
)
internal
view
returns (uint256 totalSeizeValue, AssetData[] memory seizeData)
{
_validateAssetData(position, assetData);
uint256 assetDataLen = assetData.length;
seizeData = assetData; // copy assetData and replace all type(uint).max with position asset balances
for (uint256 i; i < assetDataLen; ++i) {
address asset = seizeData[i].asset;
// ensure assetData[i] is in the position asset list
if (Position(payable(position)).hasAsset(asset) == false) {
revert RiskModule_SeizeInvalidAsset(position, asset);
}
uint256 seizeAmt = seizeData[i].amt;
if (seizeAmt == type(uint256).max) {
seizeAmt = IERC20(asset).balanceOf(position);
seizeData[i].amt = seizeAmt;
}
totalSeizeValue += riskEngine.getValueInEth(asset, seizeAmt);
}
}
// since bad debt liquidations cannot be partial, all position assets are seized
function _getBadDebtSeizeData(address position) internal view returns (AssetData[] memory) {
address[] memory positionAssets = Position(payable(position)).getPositionAssets();
uint256 positionAssetsLength = positionAssets.length;
AssetData[] memory seizeData = new AssetData[](positionAssets.length);
for (uint256 i; i < positionAssetsLength; ++i) {
address asset = positionAssets[i];
uint256 amt = IERC20(positionAssets[i]).balanceOf(position);
seizeData[i] = AssetData({ asset: asset, amt: amt });
}
return seizeData;
}
// ensure DebtData has no duplicates by enforcing an ascending order of poolIds
// ensure repaid pools are in the debt array for the position
function _validateDebtData(address position, DebtData[] memory debtData) internal view {
uint256 debtDataLen = debtData.length;
if (debtDataLen == 0) return;
uint256 lastPoolId = debtData[0].poolId;
if (Position(payable(position)).hasDebt(lastPoolId) == false) revert RiskModule_InvalidDebtData(lastPoolId);
for (uint256 i = 1; i < debtDataLen; ++i) {
uint256 poolId = debtData[i].poolId;
if (poolId <= lastPoolId) revert RiskModule_InvalidDebtData(poolId);
if (Position(payable(position)).hasDebt(poolId) == false) revert RiskModule_InvalidDebtData(poolId);
lastPoolId = poolId;
}
}
// ensure assetData has no duplicates by enforcing an ascending order of assets
// ensure seized assets are in the assets array for the position
function _validateAssetData(address position, AssetData[] memory assetData) internal view {
uint256 assetDataLen = assetData.length;
if (assetDataLen == 0) return;
address lastAsset = assetData[0].asset;
if (Position(payable(position)).hasAsset(lastAsset) == false) revert RiskModule_InvalidAssetData(lastAsset);
for (uint256 i = 1; i < assetDataLen; ++i) {
address asset = assetData[i].asset;
if (asset <= lastAsset) revert RiskModule_InvalidAssetData(asset);
if (Position(payable(position)).hasAsset(asset) == false) revert RiskModule_InvalidAssetData(asset);
lastAsset = asset;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*//////////////////////////////////////////////////////////////
IOracle
//////////////////////////////////////////////////////////////*/
/// @title IOracle
/// @notice Common interface for all oracle implementations
interface IOracle {
/// @notice Compute the equivalent ETH value for a given amount of a particular asset
/// @param asset Address of the asset to be priced
/// @param amt Amount of the given asset to be priced
/// @return valueInEth Equivalent ETH value for the given asset and amount, scaled by 18 decimals
function getValueInEth(address asset, uint256 amt) external view returns (uint256 valueInEth);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*//////////////////////////////////////////////////////////////
IRateModel
//////////////////////////////////////////////////////////////*/
/// @title IRateModel
/// @notice Common interface for all rate model implementations
interface IRateModel {
/// @notice Compute the amount of interest accrued since the last interest update
/// @param lastUpdated Timestamp of the last interest update
/// @param totalBorrows Total amount of assets borrowed from the pool
/// @param totalAssets Total amount of assets controlled by the pool
/// @return interestAccrued Amount of interest accrued since the last interest update
/// denominated in terms of the given asset
function getInterestAccrued(
uint256 lastUpdated,
uint256 totalBorrows,
uint256 totalAssets
)
external
view
returns (uint256 interestAccrued);
/// @notice Fetch the instantaneous borrow interest rate for a given pool state
/// @param totalBorrows Total amount of assets borrowed from the pool
/// @param totalAssets Total amount of assets controlled by the pool
/// @return interestRate Instantaneous interest rate for the given pool state, scaled by 18 decimals
function getInterestRate(uint256 totalBorrows, uint256 totalAssets) external view returns (uint256 interestRate);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title ERC6909
/// @notice Minimalist and gas efficient standard ERC6909 implementation.
/// @dev Forked from solmate with modified approval flows
abstract contract ERC6909 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OperatorSet(address indexed owner, address indexed operator, bool approved);
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
/*//////////////////////////////////////////////////////////////
ERC6909 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address owner => mapping(address operator => bool operatorStatus)) public isOperator;
mapping(address owner => mapping(uint256 id => uint256 balance)) public balanceOf;
mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 allowance))) public allowance;
/*//////////////////////////////////////////////////////////////
ERC6909 LOGIC
//////////////////////////////////////////////////////////////*/
function transfer(address receiver, uint256 id, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, msg.sender, receiver, id, amount);
return true;
}
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public virtual returns (bool) {
if (msg.sender != sender && !isOperator[sender][msg.sender]) {
uint256 allowed = allowance[sender][msg.sender][id];
if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount;
}
balanceOf[sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, sender, receiver, id, amount);
return true;
}
function approve(address spender, uint256 id, uint256 amount) public virtual returns (bool) {
_approve(spender, id, amount);
return true;
}
function increaseAllowance(address spender, uint256 id, uint256 value) public virtual returns (bool) {
_approve(spender, id, allowance[msg.sender][spender][id] + value);
return true;
}
function decreaseAllowance(address spender, uint256 id, uint256 value) public virtual returns (bool) {
_approve(spender, id, allowance[msg.sender][spender][id] - value);
return true;
}
function setOperator(address operator, bool approved) public virtual returns (bool) {
isOperator[msg.sender][operator] = approved;
emit OperatorSet(msg.sender, operator, approved);
return true;
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
|| interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address receiver, uint256 id, uint256 amount) internal virtual {
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, address(0), receiver, id, amount);
}
function _burn(address sender, uint256 id, uint256 amount) internal virtual {
balanceOf[sender][id] -= amount;
emit Transfer(msg.sender, sender, address(0), id, amount);
}
function _approve(address spender, uint256 id, uint256 amount) internal virtual {
allowance[msg.sender][spender][id] = amount;
emit Approval(msg.sender, spender, id, amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*//////////////////////////////////////////////////////////////
IterableSet
//////////////////////////////////////////////////////////////*/
/// @title IterableSet
/// @notice Iterable set library for address and uint256 types
library IterableSet {
/*//////////////////////////////////////////////////////////////
Address Set
//////////////////////////////////////////////////////////////*/
/// @title AddressSet
/// @notice Storage struct for iterable address set
struct AddressSet {
address[] elements; // list of elements in the set
// idxOf indexing scheme:
// idxOf[element] = index of key in self.elements + 1 OR in other words
// idxOf[element] = one-indexed location of a particular element in self.elements
// idxOf[element] = 0 denotes that an element is not present in the set
mapping(address elem => uint256 idx) idxOf;
}
/// @notice Check if the set contains a given element
function contains(AddressSet storage self, address elem) internal view returns (bool) {
return self.idxOf[elem] > 0; // idxOf[element] = 0 denotes that element is not in the set
}
/// @notice Fetch element from set by index
/// @dev Zero-indexed query to the elements array. Set does not preserve order after removals
function getByIdx(AddressSet storage self, uint256 idx) internal view returns (address) {
return self.elements[idx];
}
/// @notice Fetch all elements in the set
/// @dev Set does not preserve order after removals
function getElements(AddressSet storage self) internal view returns (address[] memory) {
return self.elements;
}
/// @notice Fetch the number of elements in the set
function length(AddressSet storage self) internal view returns (uint256) {
return self.elements.length;
}
// insertion: the element is pushed to the end of self.elements and idxOf is updated accordingly
/// @notice Insert an element into the set
/// @dev No-op if element already exists
function insert(AddressSet storage self, address elem) internal {
if (self.idxOf[elem] != 0) return; // no-op if element is already in the set
self.elements.push(elem);
self.idxOf[elem] = self.elements.length;
}
// removal: replace it with the current last element, update idxOf and call pop()
// if the element to be removed is the last element, simply update idxOf and call pop()
/// @notice Remove element from set
/// @dev No-op if element is not in the set
function remove(AddressSet storage self, address elem) internal {
if (self.idxOf[elem] == 0) return; // no-op if element is not in the set
uint256 toRemoveIdx = self.idxOf[elem] - 1; // idx of element to be removed
uint256 len = self.elements.length;
// if element to be removed is not at the end, replace it with the last element
if (toRemoveIdx != len - 1) {
address lastElem = self.elements[len - 1];
self.elements[toRemoveIdx] = lastElem;
self.idxOf[lastElem] = toRemoveIdx + 1; // idxOf mapping is 1-indexed
}
self.idxOf[elem] = 0; // idxOf[elem] = 0 denotes that it is no longer in the set
self.elements.pop(); // pop self.elements array effectively deleting elem
}
/*//////////////////////////////////////////////////////////////
Uint256 Set
//////////////////////////////////////////////////////////////*/
/// @title Uint256Set
/// @notice Storage struct for iterable uint256 set
struct Uint256Set {
uint256[] elements; // list of elements in the set
// idxOf indexing scheme:
// idxOf[element] = index of key in self.elements + 1 OR in other words
// idxOf[element] = one-indexed location of a particular element in self.elements
// idxOf[element] = 0, if element is not present in the set
mapping(uint256 elem => uint256 idx) idxOf;
}
/// @notice Check if the set contains a give element
function contains(Uint256Set storage self, uint256 elem) internal view returns (bool) {
return self.idxOf[elem] > 0; // idxOf[element] = 0 denotes that element is not in the set
}
/// @notice Fetch element from set by index
/// @dev Zero-indexed query to the elements array. Set does not preserve order after removals
function getByIdx(Uint256Set storage self, uint256 idx) internal view returns (uint256) {
return self.elements[idx];
}
/// @notice Fetch all elements in the set
/// @dev Set does not preserve order after removals
function getElements(Uint256Set storage self) internal view returns (uint256[] memory) {
return self.elements;
}
/// @notice Fetch the number of elements in the set
function length(Uint256Set storage self) internal view returns (uint256) {
return self.elements.length;
}
// insertion: the element is pushed to the end of self.elements and idxOf is updated accordingly
/// @notice Insert an element into the set
/// @dev No-op if element already exists
function insert(Uint256Set storage self, uint256 elem) internal {
if (self.idxOf[elem] != 0) return; // no-op if element is already in the set
self.elements.push(elem);
self.idxOf[elem] = self.elements.length;
}
// removal: replace it with the current last element, update idxOf and call pop()
// if the element to be removed is the last element, simply update idxOf and call pop()
/// @notice Remove element from set
/// @dev No-op if element does not exist
function remove(Uint256Set storage self, uint256 elem) internal {
if (self.idxOf[elem] == 0) return; // no-op if element is not in the set
uint256 toRemoveIdx = self.idxOf[elem] - 1; // idx of element to be removed
uint256 len = self.elements.length;
// if element to be removed is not at the end, replace it with the last element
if (toRemoveIdx != len - 1) {
uint256 lastElem = self.elements[len - 1];
self.elements[toRemoveIdx] = lastElem;
self.idxOf[lastElem] = toRemoveIdx + 1; // idxOf mapping is 1-indexed
}
self.idxOf[elem] = 0; // idxOf[elem] = 0 denotes that it is no longer in the set
self.elements.pop(); // pop self.elements array effectively deleting elem
}
}{
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"appendCBOR": false,
"bytecodeHash": "none",
"useLiteralContent": false
},
"optimizer": {
"details": {
"constantOptimizer": true,
"yul": true,
"yulDetails": {
"stackAllocation": true
}
},
"enabled": true,
"runs": 1024
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": [
"forge-std/=lib/forge-std/src/",
"@redstone-oracles-monorepo/=lib/redstone-oracles-monorepo/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
"redstone-oracles-monorepo/=lib/redstone-oracles-monorepo/"
],
"viaIR": false
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"registry_","type":"address"},{"internalType":"uint256","name":"minLtv_","type":"uint256"},{"internalType":"uint256","name":"maxLtv_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"RiskEngine_CannotBorrowPoolAsset","type":"error"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"RiskEngine_InvalidBasePool","type":"error"},{"inputs":[{"internalType":"uint256","name":"minLtv","type":"uint256"},{"internalType":"uint256","name":"maxLtv","type":"uint256"}],"name":"RiskEngine_InvalidLtvLimits","type":"error"},{"inputs":[{"internalType":"uint256","name":"ltv","type":"uint256"}],"name":"RiskEngine_LtvLimitBreached","type":"error"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"RiskEngine_LtvUpdateExpired","type":"error"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"RiskEngine_LtvUpdateTimelocked","type":"error"},{"inputs":[],"name":"RiskEngine_MaxLtvTooHigh","type":"error"},{"inputs":[],"name":"RiskEngine_MinLtvTooLow","type":"error"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"RiskEngine_NoLtvUpdate","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"RiskEngine_NoOracleFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"sender","type":"address"}],"name":"RiskEngine_OnlyPoolOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minLtv","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxLtv","type":"uint256"}],"name":"LtvBoundsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"ltv","type":"uint256"}],"name":"LtvUpdateAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"}],"name":"LtvUpdateRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"components":[{"internalType":"uint256","name":"ltv","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"}],"indexed":false,"internalType":"struct RiskEngine.LtvUpdate","name":"ltvUpdate","type":"tuple"}],"name":"LtvUpdateRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"address","name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolA","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"poolB","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isAllowed","type":"bool"}],"name":"PoolPairToggled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"pool","type":"address"}],"name":"PoolSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"registry","type":"address"}],"name":"RegistrySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"riskModule","type":"address"}],"name":"RiskModuleSet","type":"event"},{"inputs":[],"name":"SENTIMENT_POOL_KEY","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SENTIMENT_RISK_MODULE_KEY","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TIMELOCK_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TIMELOCK_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"acceptLtvUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"getPositionHealthFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"getRiskData","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amt","type":"uint256"}],"name":"getValueInEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolA","type":"uint256"},{"internalType":"uint256","name":"poolB","type":"uint256"}],"name":"isAllowedPair","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"ltvFor","outputs":[{"internalType":"uint256","name":"ltv","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"ltvUpdateFor","outputs":[{"internalType":"uint256","name":"ltv","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLtv","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minLtv","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"oracleFor","outputs":[{"internalType":"address","name":"oracle","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pool","outputs":[{"internalType":"contract Pool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"registry","outputs":[{"internalType":"contract Registry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"}],"name":"rejectLtvUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"ltv","type":"uint256"}],"name":"requestLtvUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"riskModule","outputs":[{"internalType":"contract RiskModule","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minLtv","type":"uint256"},{"internalType":"uint256","name":"_maxLtv","type":"uint256"}],"name":"setLtvBounds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"oracle","type":"address"}],"name":"setOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRegistry","type":"address"}],"name":"setRegistry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolA","type":"uint256"},{"internalType":"uint256","name":"poolB","type":"uint256"}],"name":"toggleAllowedPoolPair","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateFromRegistry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"position","type":"address"},{"components":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct DebtData[]","name":"debtData","type":"tuple[]"}],"name":"validateBadDebtLiquidation","outputs":[{"components":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct DebtData[]","name":"","type":"tuple[]"},{"components":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct AssetData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"position","type":"address"},{"components":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct DebtData[]","name":"debtData","type":"tuple[]"},{"components":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct AssetData[]","name":"assetData","type":"tuple[]"}],"name":"validateLiquidation","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"components":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct DebtData[]","name":"","type":"tuple[]"},{"components":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amt","type":"uint256"}],"internalType":"struct AssetData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}]Contract Creation Code
608060405234801562000010575f80fd5b5060405162001dd538038062001dd583398101604081905262000033916200016d565b6200003e336200011e565b815f036200005f5760405163e2dc5a7b60e01b815260040160405180910390fd5b670de0b6b3a76400008110620000885760405163159d7cf360e11b815260040160405180910390fd5b808210620000b7576040516308e4b6d560e21b8152600481018390526024810182905260440160405180910390fd5b600380546001600160a01b0319166001600160a01b0385161790556001829055600281905560408051838152602081018390527f76ddd1287961c754a16c5b15eb241d749a61e410e14fabb9e268f58145a3b537910160405180910390a1505050620001ae565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f805f6060848603121562000180575f80fd5b83516001600160a01b038116811462000197575f80fd5b602085015160409095015190969495509392505050565b611c1980620001bc5f395ff3fe608060405234801561000f575f80fd5b50600436106101c6575f3560e01c8063715018a6116100fe5780639ed1a9851161009e578063d95906551161006e578063d959065514610471578063f2fde38b1461049f578063f433262f146104b2578063f43c4eb4146104ba575f80fd5b80639ed1a985146103dd578063a91ee0dc146103f0578063aed019b914610403578063b997454f1461042b575f80fd5b80637b103999116100d95780637b1039991461036a578063853cb6ba1461037d5780638da5cb5b146103ba5780639630b472146103ca575f80fd5b8063715018a61461033c5780637512370014610344578063766121e214610357575f80fd5b80633a12c6da116101695780634623c81e116101445780634623c81e146102f95780634d1ccac5146103035780635c38eb3a14610316578063657df9d914610329575f80fd5b80633a12c6da146102d157806343862a2c146102da57806344148a92146102ef575f80fd5b80632794bc44116101a45780632794bc4414610236578063290c0409146102605780633483cbb2146102835780633617dfac146102aa575f80fd5b806304ab80f5146101ca5780630e71b464146101f457806316f0115b1461020b575b5f80fd5b6101dd6101d8366004611584565b6104cd565b6040516101eb929190611666565b60405180910390f35b6101fd60015481565b6040519081526020016101eb565b60045461021e906001600160a01b031681565b6040516001600160a01b0390911681526020016101eb565b6101fd610244366004611693565b600760209081525f928352604080842090915290825290205481565b61027361026e3660046116c1565b61056c565b6040516101eb949392919061173e565b6101fd7f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972881565b6101fd7f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7781565b6101fd60025481565b6102ed6102e8366004611779565b61061a565b005b6101fd6203f48081565b6101fd6201518081565b6101fd6103113660046117ae565b610902565b6102ed6103243660046117d0565b61098d565b6102ed610337366004611693565b6109fb565b6102ed610af6565b6101fd6103523660046117fc565b610b09565b6102ed610365366004611826565b610beb565b60035461021e906001600160a01b031681565b6103aa61038b366004611826565b600960209081525f928352604080842090915290825290205460ff1681565b60405190151581526020016101eb565b5f546001600160a01b031661021e565b6102ed6103d8366004611826565b610d9f565b60055461021e906001600160a01b031681565b6102ed6103fe3660046117ae565b610eab565b61021e6104113660046117ae565b60066020525f90815260409020546001600160a01b031681565b61045c610439366004611693565b600860209081525f92835260408084209091529082529020805460019091015482565b604080519283526020830191909152016101eb565b61048461047f3660046117ae565b610f07565b604080519384526020840192909252908201526060016101eb565b6102ed6104ad3660046117ae565b610f9f565b6102ed61102f565b6102ed6104c8366004611693565b6111ee565b6005546040517f04ab80f500000000000000000000000000000000000000000000000000000000815260609182916001600160a01b03909116906304ab80f59061051f9088908890889060040161187a565b5f60405180830381865afa158015610539573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526105609190810190611a3a565b91509150935093915050565b6005546040517f290c04090000000000000000000000000000000000000000000000000000000081525f91829160609182916001600160a01b039091169063290c0409906105c6908c908c908c908c908c90600401611a9a565b5f60405180830381865afa1580156105e0573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526106079190810190611b14565b929c919b50995090975095505050505050565b600480546040516331a9108f60e11b81529182018590526001600160a01b031690636352211e90602401602060405180830381865afa15801561065f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106839190611b86565b6001600160a01b0316336001600160a01b0316146106c25760405163fa5947cd60e01b8152600481018490523360248201526044015b60405180910390fd5b6001600160a01b038281165f908152600660205260409020541661070457604051630320697360e01b81526001600160a01b03831660048201526024016106b9565b600154811080610715575060025481115b1561074f576040517fc3f8e7ec000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b600480546040517f2307b4f90000000000000000000000000000000000000000000000000000000081529182018590526001600160a01b0384811692911690632307b4f990602401602060405180830381865afa1580156107b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190611b86565b6001600160a01b031603610819576040517f2e762e16000000000000000000000000000000000000000000000000000000008152600481018490526024016106b9565b6040805180820182525f8082526020808301829052868252600781528382206001600160a01b038716835290529182205490910361086a57506040805180820190915281815242602082015261088f565b6040518060400160405280838152602001620151804261088a9190611ba1565b905290505b5f8481526008602090815260408083206001600160a01b0387168085529083529281902084518155918401516001909201919091555185907f7cec4c2dc59d75545a22de1e92bdb560e111db5a849d19a5fa3383d86fde1584906108f4908590611bc0565b60405180910390a350505050565b6005546040517f4d1ccac50000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f921690634d1ccac590602401602060405180830381865afa158015610963573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109879190611bd7565b92915050565b610995611480565b6001600160a01b038281165f8181526006602090815260409182902080546001600160a01b0319169486169485179055905192835290917fc1d3048301c0d23629a2532c8defa6d68f8e1a0e4157918769e9fb1b2eeb888e910160405180910390a25050565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015610a40573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a649190611b86565b6001600160a01b0316336001600160a01b031614610a9e5760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b5f8281526008602090815260408083206001600160a01b038516808552925280832083815560010183905551909184917f8fc28ba5f3d7b339e7c15bafa3559ffda2a8828893f76efe63258e587062999c9190a35050565b610afe611480565b610b075f6114d9565b565b5f815f03610b1857505f610987565b6001600160a01b038084165f908152600660205260409020541680610b5b57604051630320697360e01b81526001600160a01b03851660048201526024016106b9565b6040517f751237000000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015260248201859052821690637512370090604401602060405180830381865afa158015610bbf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610be39190611bd7565b949350505050565b600480546040516331a9108f60e11b815291820184905233916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610c34573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c589190611b86565b6001600160a01b031614610c885760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b600480546040516331a9108f60e11b81529182018390525f916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610cd1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cf59190611b86565b6001600160a01b031603610d38576040517f394d8c7e000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b5f828152600960209081526040808320848452825291829020805460ff8082161560ff1990921682179092559251921615158252829184917f055ceb561d17c4f01875b36bf90963284328fbdec241334d73a5a18d3785c5e4910160405180910390a35050565b610da7611480565b815f03610de0576040517fe2dc5a7b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b670de0b6b3a76400008110610e21576040517f2b3af9e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808210610e64576040517f2392db5400000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016106b9565b6001829055600281905560408051838152602081018390527f76ddd1287961c754a16c5b15eb241d749a61e410e14fabb9e268f58145a3b537910160405180910390a15050565b610eb3611480565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b9060200160405180910390a150565b6005546040517fd95906550000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f92839283929091169063d959065590602401606060405180830381865afa158015610f6e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f929190611bee565b9250925092509193909250565b610fa7611480565b6001600160a01b0381166110235760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106b9565b61102c816114d9565b50565b6003546040516318dee17b60e01b81527f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972860048201526001600160a01b03909116906318dee17b90602401602060405180830381865afa158015611095573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110b99190611b86565b600480546001600160a01b0319166001600160a01b039283161781556003546040516318dee17b60e01b81527f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7792810192909252909116906318dee17b90602401602060405180830381865afa158015611135573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111599190611b86565b600580546001600160a01b0319166001600160a01b03928316179055600454604051911681527f025f89b99c8ce32af8da7624f4575b920a86ebf07870d85a9fb545fee349ddce9060200160405180910390a16005546040516001600160a01b0390911681527f808fc0874768c225409d9bf87fc94b6cdc40cb955dd7960727704b534b352b9b9060200160405180910390a1565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015611233573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112579190611b86565b6001600160a01b0316336001600160a01b0316146112915760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b6001600160a01b038181165f90815260066020526040902054166112d357604051630320697360e01b81526001600160a01b03821660048201526024016106b9565b5f8281526008602090815260408083206001600160a01b03851684528252808320815180830190925280548252600101549181018290529103611354576040517f188505a6000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b42816020015111156113a4576040517f03c287a9000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b6203f48081602001516113b79190611ba1565b421115611402576040517f24aad6d1000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b80515f8481526007602090815260408083206001600160a01b03871680855290835281842094909455868352600882528083208484528252808320838155600101929092558351915191825285917ff0a6a76c322607a7ac64afb307da5dde6761948aa16f09f9bb66e7ac90f51e46910160405180910390a3505050565b5f546001600160a01b03163314610b075760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106b9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461102c575f80fd5b5f8083601f84011261154c575f80fd5b50813567ffffffffffffffff811115611563575f80fd5b6020830191508360208260061b850101111561157d575f80fd5b9250929050565b5f805f60408486031215611596575f80fd5b83356115a181611528565b9250602084013567ffffffffffffffff8111156115bc575f80fd5b6115c88682870161153c565b9497909650939450505050565b5f815180845260208085019450602084015f5b838110156116185761160587835180518252602090810151910152565b60409690960195908201906001016115e8565b509495945050505050565b5f815180845260208085019450602084015f5b8381101561161857815180516001600160a01b031688528301518388015260409096019590820190600101611636565b604081525f61167860408301856115d5565b828103602084015261168a8185611623565b95945050505050565b5f80604083850312156116a4575f80fd5b8235915060208301356116b681611528565b809150509250929050565b5f805f805f606086880312156116d5575f80fd5b85356116e081611528565b9450602086013567ffffffffffffffff808211156116fc575f80fd5b61170889838a0161153c565b90965094506040880135915080821115611720575f80fd5b5061172d8882890161153c565b969995985093965092949392505050565b848152836020820152608060408201525f61175c60808301856115d5565b828103606084015261176e8185611623565b979650505050505050565b5f805f6060848603121561178b575f80fd5b83359250602084013561179d81611528565b929592945050506040919091013590565b5f602082840312156117be575f80fd5b81356117c981611528565b9392505050565b5f80604083850312156117e1575f80fd5b82356117ec81611528565b915060208301356116b681611528565b5f806040838503121561180d575f80fd5b823561181881611528565b946020939093013593505050565b5f8060408385031215611837575f80fd5b50508035926020909101359150565b8183525f60208085019450825f5b858110156116185781358752828201358388015260409687019690910190600101611854565b6001600160a01b0384168152604060208201525f61168a604083018486611846565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff811182821017156118d3576118d361189c565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156119025761190261189c565b604052919050565b5f67ffffffffffffffff8211156119235761192361189c565b5060051b60200190565b5f82601f83011261193c575f80fd5b8151602061195161194c8361190a565b6118d9565b82815260069290921b8401810191818101908684111561196f575f80fd5b8286015b848110156119ac576040818903121561198a575f80fd5b6119926118b0565b815181528482015185820152835291830191604001611973565b509695505050505050565b5f82601f8301126119c6575f80fd5b815160206119d661194c8361190a565b82815260069290921b840181019181810190868411156119f4575f80fd5b8286015b848110156119ac5760408189031215611a0f575f80fd5b611a176118b0565b8151611a2281611528565b815281850151858201528352918301916040016119f8565b5f8060408385031215611a4b575f80fd5b825167ffffffffffffffff80821115611a62575f80fd5b611a6e8683870161192d565b93506020850151915080821115611a83575f80fd5b50611a90858286016119b7565b9150509250929050565b5f6001600160a01b038088168352602060606020850152611abf60608501888a611846565b84810360408681019190915286825287916020015f5b88811015611b04578335611ae881611528565b8616825283850135858301529282019290820190600101611ad5565b509b9a5050505050505050505050565b5f805f8060808587031215611b27575f80fd5b8451935060208501519250604085015167ffffffffffffffff80821115611b4c575f80fd5b611b588883890161192d565b93506060870151915080821115611b6d575f80fd5b50611b7a878288016119b7565b91505092959194509250565b5f60208284031215611b96575f80fd5b81516117c981611528565b8082018082111561098757634e487b7160e01b5f52601160045260245ffd5b815181526020808301519082015260408101610987565b5f60208284031215611be7575f80fd5b5051919050565b5f805f60608486031215611c00575f80fd5b835192506020840151915060408401519050925092509256000000000000000000000000121430becc13238ef81e40a968d019fc8dfb2605000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000d2f13f7789f0000
Deployed Bytecode
0x608060405234801561000f575f80fd5b50600436106101c6575f3560e01c8063715018a6116100fe5780639ed1a9851161009e578063d95906551161006e578063d959065514610471578063f2fde38b1461049f578063f433262f146104b2578063f43c4eb4146104ba575f80fd5b80639ed1a985146103dd578063a91ee0dc146103f0578063aed019b914610403578063b997454f1461042b575f80fd5b80637b103999116100d95780637b1039991461036a578063853cb6ba1461037d5780638da5cb5b146103ba5780639630b472146103ca575f80fd5b8063715018a61461033c5780637512370014610344578063766121e214610357575f80fd5b80633a12c6da116101695780634623c81e116101445780634623c81e146102f95780634d1ccac5146103035780635c38eb3a14610316578063657df9d914610329575f80fd5b80633a12c6da146102d157806343862a2c146102da57806344148a92146102ef575f80fd5b80632794bc44116101a45780632794bc4414610236578063290c0409146102605780633483cbb2146102835780633617dfac146102aa575f80fd5b806304ab80f5146101ca5780630e71b464146101f457806316f0115b1461020b575b5f80fd5b6101dd6101d8366004611584565b6104cd565b6040516101eb929190611666565b60405180910390f35b6101fd60015481565b6040519081526020016101eb565b60045461021e906001600160a01b031681565b6040516001600160a01b0390911681526020016101eb565b6101fd610244366004611693565b600760209081525f928352604080842090915290825290205481565b61027361026e3660046116c1565b61056c565b6040516101eb949392919061173e565b6101fd7f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972881565b6101fd7f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7781565b6101fd60025481565b6102ed6102e8366004611779565b61061a565b005b6101fd6203f48081565b6101fd6201518081565b6101fd6103113660046117ae565b610902565b6102ed6103243660046117d0565b61098d565b6102ed610337366004611693565b6109fb565b6102ed610af6565b6101fd6103523660046117fc565b610b09565b6102ed610365366004611826565b610beb565b60035461021e906001600160a01b031681565b6103aa61038b366004611826565b600960209081525f928352604080842090915290825290205460ff1681565b60405190151581526020016101eb565b5f546001600160a01b031661021e565b6102ed6103d8366004611826565b610d9f565b60055461021e906001600160a01b031681565b6102ed6103fe3660046117ae565b610eab565b61021e6104113660046117ae565b60066020525f90815260409020546001600160a01b031681565b61045c610439366004611693565b600860209081525f92835260408084209091529082529020805460019091015482565b604080519283526020830191909152016101eb565b61048461047f3660046117ae565b610f07565b604080519384526020840192909252908201526060016101eb565b6102ed6104ad3660046117ae565b610f9f565b6102ed61102f565b6102ed6104c8366004611693565b6111ee565b6005546040517f04ab80f500000000000000000000000000000000000000000000000000000000815260609182916001600160a01b03909116906304ab80f59061051f9088908890889060040161187a565b5f60405180830381865afa158015610539573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526105609190810190611a3a565b91509150935093915050565b6005546040517f290c04090000000000000000000000000000000000000000000000000000000081525f91829160609182916001600160a01b039091169063290c0409906105c6908c908c908c908c908c90600401611a9a565b5f60405180830381865afa1580156105e0573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526106079190810190611b14565b929c919b50995090975095505050505050565b600480546040516331a9108f60e11b81529182018590526001600160a01b031690636352211e90602401602060405180830381865afa15801561065f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106839190611b86565b6001600160a01b0316336001600160a01b0316146106c25760405163fa5947cd60e01b8152600481018490523360248201526044015b60405180910390fd5b6001600160a01b038281165f908152600660205260409020541661070457604051630320697360e01b81526001600160a01b03831660048201526024016106b9565b600154811080610715575060025481115b1561074f576040517fc3f8e7ec000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b600480546040517f2307b4f90000000000000000000000000000000000000000000000000000000081529182018590526001600160a01b0384811692911690632307b4f990602401602060405180830381865afa1580156107b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190611b86565b6001600160a01b031603610819576040517f2e762e16000000000000000000000000000000000000000000000000000000008152600481018490526024016106b9565b6040805180820182525f8082526020808301829052868252600781528382206001600160a01b038716835290529182205490910361086a57506040805180820190915281815242602082015261088f565b6040518060400160405280838152602001620151804261088a9190611ba1565b905290505b5f8481526008602090815260408083206001600160a01b0387168085529083529281902084518155918401516001909201919091555185907f7cec4c2dc59d75545a22de1e92bdb560e111db5a849d19a5fa3383d86fde1584906108f4908590611bc0565b60405180910390a350505050565b6005546040517f4d1ccac50000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f921690634d1ccac590602401602060405180830381865afa158015610963573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109879190611bd7565b92915050565b610995611480565b6001600160a01b038281165f8181526006602090815260409182902080546001600160a01b0319169486169485179055905192835290917fc1d3048301c0d23629a2532c8defa6d68f8e1a0e4157918769e9fb1b2eeb888e910160405180910390a25050565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015610a40573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a649190611b86565b6001600160a01b0316336001600160a01b031614610a9e5760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b5f8281526008602090815260408083206001600160a01b038516808552925280832083815560010183905551909184917f8fc28ba5f3d7b339e7c15bafa3559ffda2a8828893f76efe63258e587062999c9190a35050565b610afe611480565b610b075f6114d9565b565b5f815f03610b1857505f610987565b6001600160a01b038084165f908152600660205260409020541680610b5b57604051630320697360e01b81526001600160a01b03851660048201526024016106b9565b6040517f751237000000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015260248201859052821690637512370090604401602060405180830381865afa158015610bbf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610be39190611bd7565b949350505050565b600480546040516331a9108f60e11b815291820184905233916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610c34573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c589190611b86565b6001600160a01b031614610c885760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b600480546040516331a9108f60e11b81529182018390525f916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610cd1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cf59190611b86565b6001600160a01b031603610d38576040517f394d8c7e000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b5f828152600960209081526040808320848452825291829020805460ff8082161560ff1990921682179092559251921615158252829184917f055ceb561d17c4f01875b36bf90963284328fbdec241334d73a5a18d3785c5e4910160405180910390a35050565b610da7611480565b815f03610de0576040517fe2dc5a7b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b670de0b6b3a76400008110610e21576040517f2b3af9e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808210610e64576040517f2392db5400000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016106b9565b6001829055600281905560408051838152602081018390527f76ddd1287961c754a16c5b15eb241d749a61e410e14fabb9e268f58145a3b537910160405180910390a15050565b610eb3611480565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b9060200160405180910390a150565b6005546040517fd95906550000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f92839283929091169063d959065590602401606060405180830381865afa158015610f6e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f929190611bee565b9250925092509193909250565b610fa7611480565b6001600160a01b0381166110235760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106b9565b61102c816114d9565b50565b6003546040516318dee17b60e01b81527f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972860048201526001600160a01b03909116906318dee17b90602401602060405180830381865afa158015611095573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110b99190611b86565b600480546001600160a01b0319166001600160a01b039283161781556003546040516318dee17b60e01b81527f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7792810192909252909116906318dee17b90602401602060405180830381865afa158015611135573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111599190611b86565b600580546001600160a01b0319166001600160a01b03928316179055600454604051911681527f025f89b99c8ce32af8da7624f4575b920a86ebf07870d85a9fb545fee349ddce9060200160405180910390a16005546040516001600160a01b0390911681527f808fc0874768c225409d9bf87fc94b6cdc40cb955dd7960727704b534b352b9b9060200160405180910390a1565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015611233573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112579190611b86565b6001600160a01b0316336001600160a01b0316146112915760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b6001600160a01b038181165f90815260066020526040902054166112d357604051630320697360e01b81526001600160a01b03821660048201526024016106b9565b5f8281526008602090815260408083206001600160a01b03851684528252808320815180830190925280548252600101549181018290529103611354576040517f188505a6000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b42816020015111156113a4576040517f03c287a9000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b6203f48081602001516113b79190611ba1565b421115611402576040517f24aad6d1000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b80515f8481526007602090815260408083206001600160a01b03871680855290835281842094909455868352600882528083208484528252808320838155600101929092558351915191825285917ff0a6a76c322607a7ac64afb307da5dde6761948aa16f09f9bb66e7ac90f51e46910160405180910390a3505050565b5f546001600160a01b03163314610b075760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106b9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461102c575f80fd5b5f8083601f84011261154c575f80fd5b50813567ffffffffffffffff811115611563575f80fd5b6020830191508360208260061b850101111561157d575f80fd5b9250929050565b5f805f60408486031215611596575f80fd5b83356115a181611528565b9250602084013567ffffffffffffffff8111156115bc575f80fd5b6115c88682870161153c565b9497909650939450505050565b5f815180845260208085019450602084015f5b838110156116185761160587835180518252602090810151910152565b60409690960195908201906001016115e8565b509495945050505050565b5f815180845260208085019450602084015f5b8381101561161857815180516001600160a01b031688528301518388015260409096019590820190600101611636565b604081525f61167860408301856115d5565b828103602084015261168a8185611623565b95945050505050565b5f80604083850312156116a4575f80fd5b8235915060208301356116b681611528565b809150509250929050565b5f805f805f606086880312156116d5575f80fd5b85356116e081611528565b9450602086013567ffffffffffffffff808211156116fc575f80fd5b61170889838a0161153c565b90965094506040880135915080821115611720575f80fd5b5061172d8882890161153c565b969995985093965092949392505050565b848152836020820152608060408201525f61175c60808301856115d5565b828103606084015261176e8185611623565b979650505050505050565b5f805f6060848603121561178b575f80fd5b83359250602084013561179d81611528565b929592945050506040919091013590565b5f602082840312156117be575f80fd5b81356117c981611528565b9392505050565b5f80604083850312156117e1575f80fd5b82356117ec81611528565b915060208301356116b681611528565b5f806040838503121561180d575f80fd5b823561181881611528565b946020939093013593505050565b5f8060408385031215611837575f80fd5b50508035926020909101359150565b8183525f60208085019450825f5b858110156116185781358752828201358388015260409687019690910190600101611854565b6001600160a01b0384168152604060208201525f61168a604083018486611846565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff811182821017156118d3576118d361189c565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156119025761190261189c565b604052919050565b5f67ffffffffffffffff8211156119235761192361189c565b5060051b60200190565b5f82601f83011261193c575f80fd5b8151602061195161194c8361190a565b6118d9565b82815260069290921b8401810191818101908684111561196f575f80fd5b8286015b848110156119ac576040818903121561198a575f80fd5b6119926118b0565b815181528482015185820152835291830191604001611973565b509695505050505050565b5f82601f8301126119c6575f80fd5b815160206119d661194c8361190a565b82815260069290921b840181019181810190868411156119f4575f80fd5b8286015b848110156119ac5760408189031215611a0f575f80fd5b611a176118b0565b8151611a2281611528565b815281850151858201528352918301916040016119f8565b5f8060408385031215611a4b575f80fd5b825167ffffffffffffffff80821115611a62575f80fd5b611a6e8683870161192d565b93506020850151915080821115611a83575f80fd5b50611a90858286016119b7565b9150509250929050565b5f6001600160a01b038088168352602060606020850152611abf60608501888a611846565b84810360408681019190915286825287916020015f5b88811015611b04578335611ae881611528565b8616825283850135858301529282019290820190600101611ad5565b509b9a5050505050505050505050565b5f805f8060808587031215611b27575f80fd5b8451935060208501519250604085015167ffffffffffffffff80821115611b4c575f80fd5b611b588883890161192d565b93506060870151915080821115611b6d575f80fd5b50611b7a878288016119b7565b91505092959194509250565b5f60208284031215611b96575f80fd5b81516117c981611528565b8082018082111561098757634e487b7160e01b5f52601160045260245ffd5b815181526020808301519082015260408101610987565b5f60208284031215611be7575f80fd5b5051919050565b5f805f60608486031215611c00575f80fd5b835192506020840151915060408401519050925092509256
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000121430becc13238ef81e40a968d019fc8dfb2605000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000d2f13f7789f0000
-----Decoded View---------------
Arg [0] : registry_ (address): 0x121430beCc13238ef81e40A968d019Fc8dFB2605
Arg [1] : minLtv_ (uint256): 100000000000000000
Arg [2] : maxLtv_ (uint256): 950000000000000000
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000121430becc13238ef81e40a968d019fc8dfb2605
Arg [1] : 000000000000000000000000000000000000000000000000016345785d8a0000
Arg [2] : 0000000000000000000000000000000000000000000000000d2f13f7789f0000
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in HYPE
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.