Source Code
Overview
HYPE Balance
HYPE Value
$0.00Latest 12 from a total of 12 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Set Oracle | 1639039 | 305 days ago | IN | 0 HYPE | 0.0000031 | ||||
| Set Oracle | 1639039 | 305 days ago | IN | 0 HYPE | 0.0000031 | ||||
| Set Oracle | 1632405 | 305 days ago | IN | 0 HYPE | 0.00000328 | ||||
| Set Oracle | 1631971 | 305 days ago | IN | 0 HYPE | 0.0000031 | ||||
| Set Oracle | 1631816 | 305 days ago | IN | 0 HYPE | 0.00000282 | ||||
| Set Oracle | 1631785 | 305 days ago | IN | 0 HYPE | 0.0000031 | ||||
| Accept Ltv Updat... | 756800 | 325 days ago | IN | 0 HYPE | 0.00000643 | ||||
| Request Ltv Upda... | 756800 | 325 days ago | IN | 0 HYPE | 0.00000966 | ||||
| Set Oracle | 755586 | 325 days ago | IN | 0 HYPE | 0.00000754 | ||||
| Set Oracle | 755555 | 325 days ago | IN | 0 HYPE | 0.00000481 | ||||
| Update From Regi... | 755369 | 325 days ago | IN | 0 HYPE | 0.00000784 | ||||
| Transfer Ownersh... | 754780 | 325 days ago | IN | 0 HYPE | 0.00000258 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x71Bc92B8...39b1976B2 The constructor portion of the code might be different and could alter the actual behaviour of the contract
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.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.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) (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
0x608060405234801562000010575f80fd5b5060405162001dd538038062001dd583398101604081905262000033916200016d565b6200003e336200011e565b815f036200005f5760405163e2dc5a7b60e01b815260040160405180910390fd5b670de0b6b3a76400008110620000885760405163159d7cf360e11b815260040160405180910390fd5b808210620000b7576040516308e4b6d560e21b8152600481018390526024810182905260440160405180910390fd5b600380546001600160a01b0319166001600160a01b0385161790556001829055600281905560408051838152602081018390527f76ddd1287961c754a16c5b15eb241d749a61e410e14fabb9e268f58145a3b537910160405180910390a1505050620001ae565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f805f6060848603121562000180575f80fd5b83516001600160a01b038116811462000197575f80fd5b602085015160409095015190969495509392505050565b611c1980620001bc5f395ff3fe608060405234801561000f575f80fd5b50600436106101c6575f3560e01c8063715018a6116100fe5780639ed1a9851161009e578063d95906551161006e578063d959065514610471578063f2fde38b1461049f578063f433262f146104b2578063f43c4eb4146104ba575f80fd5b80639ed1a985146103dd578063a91ee0dc146103f0578063aed019b914610403578063b997454f1461042b575f80fd5b80637b103999116100d95780637b1039991461036a578063853cb6ba1461037d5780638da5cb5b146103ba5780639630b472146103ca575f80fd5b8063715018a61461033c5780637512370014610344578063766121e214610357575f80fd5b80633a12c6da116101695780634623c81e116101445780634623c81e146102f95780634d1ccac5146103035780635c38eb3a14610316578063657df9d914610329575f80fd5b80633a12c6da146102d157806343862a2c146102da57806344148a92146102ef575f80fd5b80632794bc44116101a45780632794bc4414610236578063290c0409146102605780633483cbb2146102835780633617dfac146102aa575f80fd5b806304ab80f5146101ca5780630e71b464146101f457806316f0115b1461020b575b5f80fd5b6101dd6101d8366004611584565b6104cd565b6040516101eb929190611666565b60405180910390f35b6101fd60015481565b6040519081526020016101eb565b60045461021e906001600160a01b031681565b6040516001600160a01b0390911681526020016101eb565b6101fd610244366004611693565b600760209081525f928352604080842090915290825290205481565b61027361026e3660046116c1565b61056c565b6040516101eb949392919061173e565b6101fd7f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972881565b6101fd7f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7781565b6101fd60025481565b6102ed6102e8366004611779565b61061a565b005b6101fd6203f48081565b6101fd6201518081565b6101fd6103113660046117ae565b610902565b6102ed6103243660046117d0565b61098d565b6102ed610337366004611693565b6109fb565b6102ed610af6565b6101fd6103523660046117fc565b610b09565b6102ed610365366004611826565b610beb565b60035461021e906001600160a01b031681565b6103aa61038b366004611826565b600960209081525f928352604080842090915290825290205460ff1681565b60405190151581526020016101eb565b5f546001600160a01b031661021e565b6102ed6103d8366004611826565b610d9f565b60055461021e906001600160a01b031681565b6102ed6103fe3660046117ae565b610eab565b61021e6104113660046117ae565b60066020525f90815260409020546001600160a01b031681565b61045c610439366004611693565b600860209081525f92835260408084209091529082529020805460019091015482565b604080519283526020830191909152016101eb565b61048461047f3660046117ae565b610f07565b604080519384526020840192909252908201526060016101eb565b6102ed6104ad3660046117ae565b610f9f565b6102ed61102f565b6102ed6104c8366004611693565b6111ee565b6005546040517f04ab80f500000000000000000000000000000000000000000000000000000000815260609182916001600160a01b03909116906304ab80f59061051f9088908890889060040161187a565b5f60405180830381865afa158015610539573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526105609190810190611a3a565b91509150935093915050565b6005546040517f290c04090000000000000000000000000000000000000000000000000000000081525f91829160609182916001600160a01b039091169063290c0409906105c6908c908c908c908c908c90600401611a9a565b5f60405180830381865afa1580156105e0573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526106079190810190611b14565b929c919b50995090975095505050505050565b600480546040516331a9108f60e11b81529182018590526001600160a01b031690636352211e90602401602060405180830381865afa15801561065f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106839190611b86565b6001600160a01b0316336001600160a01b0316146106c25760405163fa5947cd60e01b8152600481018490523360248201526044015b60405180910390fd5b6001600160a01b038281165f908152600660205260409020541661070457604051630320697360e01b81526001600160a01b03831660048201526024016106b9565b600154811080610715575060025481115b1561074f576040517fc3f8e7ec000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b600480546040517f2307b4f90000000000000000000000000000000000000000000000000000000081529182018590526001600160a01b0384811692911690632307b4f990602401602060405180830381865afa1580156107b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190611b86565b6001600160a01b031603610819576040517f2e762e16000000000000000000000000000000000000000000000000000000008152600481018490526024016106b9565b6040805180820182525f8082526020808301829052868252600781528382206001600160a01b038716835290529182205490910361086a57506040805180820190915281815242602082015261088f565b6040518060400160405280838152602001620151804261088a9190611ba1565b905290505b5f8481526008602090815260408083206001600160a01b0387168085529083529281902084518155918401516001909201919091555185907f7cec4c2dc59d75545a22de1e92bdb560e111db5a849d19a5fa3383d86fde1584906108f4908590611bc0565b60405180910390a350505050565b6005546040517f4d1ccac50000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f921690634d1ccac590602401602060405180830381865afa158015610963573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109879190611bd7565b92915050565b610995611480565b6001600160a01b038281165f8181526006602090815260409182902080546001600160a01b0319169486169485179055905192835290917fc1d3048301c0d23629a2532c8defa6d68f8e1a0e4157918769e9fb1b2eeb888e910160405180910390a25050565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015610a40573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a649190611b86565b6001600160a01b0316336001600160a01b031614610a9e5760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b5f8281526008602090815260408083206001600160a01b038516808552925280832083815560010183905551909184917f8fc28ba5f3d7b339e7c15bafa3559ffda2a8828893f76efe63258e587062999c9190a35050565b610afe611480565b610b075f6114d9565b565b5f815f03610b1857505f610987565b6001600160a01b038084165f908152600660205260409020541680610b5b57604051630320697360e01b81526001600160a01b03851660048201526024016106b9565b6040517f751237000000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015260248201859052821690637512370090604401602060405180830381865afa158015610bbf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610be39190611bd7565b949350505050565b600480546040516331a9108f60e11b815291820184905233916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610c34573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c589190611b86565b6001600160a01b031614610c885760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b600480546040516331a9108f60e11b81529182018390525f916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610cd1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cf59190611b86565b6001600160a01b031603610d38576040517f394d8c7e000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b5f828152600960209081526040808320848452825291829020805460ff8082161560ff1990921682179092559251921615158252829184917f055ceb561d17c4f01875b36bf90963284328fbdec241334d73a5a18d3785c5e4910160405180910390a35050565b610da7611480565b815f03610de0576040517fe2dc5a7b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b670de0b6b3a76400008110610e21576040517f2b3af9e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808210610e64576040517f2392db5400000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016106b9565b6001829055600281905560408051838152602081018390527f76ddd1287961c754a16c5b15eb241d749a61e410e14fabb9e268f58145a3b537910160405180910390a15050565b610eb3611480565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b9060200160405180910390a150565b6005546040517fd95906550000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f92839283929091169063d959065590602401606060405180830381865afa158015610f6e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f929190611bee565b9250925092509193909250565b610fa7611480565b6001600160a01b0381166110235760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106b9565b61102c816114d9565b50565b6003546040516318dee17b60e01b81527f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972860048201526001600160a01b03909116906318dee17b90602401602060405180830381865afa158015611095573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110b99190611b86565b600480546001600160a01b0319166001600160a01b039283161781556003546040516318dee17b60e01b81527f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7792810192909252909116906318dee17b90602401602060405180830381865afa158015611135573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111599190611b86565b600580546001600160a01b0319166001600160a01b03928316179055600454604051911681527f025f89b99c8ce32af8da7624f4575b920a86ebf07870d85a9fb545fee349ddce9060200160405180910390a16005546040516001600160a01b0390911681527f808fc0874768c225409d9bf87fc94b6cdc40cb955dd7960727704b534b352b9b9060200160405180910390a1565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015611233573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112579190611b86565b6001600160a01b0316336001600160a01b0316146112915760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b6001600160a01b038181165f90815260066020526040902054166112d357604051630320697360e01b81526001600160a01b03821660048201526024016106b9565b5f8281526008602090815260408083206001600160a01b03851684528252808320815180830190925280548252600101549181018290529103611354576040517f188505a6000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b42816020015111156113a4576040517f03c287a9000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b6203f48081602001516113b79190611ba1565b421115611402576040517f24aad6d1000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b80515f8481526007602090815260408083206001600160a01b03871680855290835281842094909455868352600882528083208484528252808320838155600101929092558351915191825285917ff0a6a76c322607a7ac64afb307da5dde6761948aa16f09f9bb66e7ac90f51e46910160405180910390a3505050565b5f546001600160a01b03163314610b075760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106b9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461102c575f80fd5b5f8083601f84011261154c575f80fd5b50813567ffffffffffffffff811115611563575f80fd5b6020830191508360208260061b850101111561157d575f80fd5b9250929050565b5f805f60408486031215611596575f80fd5b83356115a181611528565b9250602084013567ffffffffffffffff8111156115bc575f80fd5b6115c88682870161153c565b9497909650939450505050565b5f815180845260208085019450602084015f5b838110156116185761160587835180518252602090810151910152565b60409690960195908201906001016115e8565b509495945050505050565b5f815180845260208085019450602084015f5b8381101561161857815180516001600160a01b031688528301518388015260409096019590820190600101611636565b604081525f61167860408301856115d5565b828103602084015261168a8185611623565b95945050505050565b5f80604083850312156116a4575f80fd5b8235915060208301356116b681611528565b809150509250929050565b5f805f805f606086880312156116d5575f80fd5b85356116e081611528565b9450602086013567ffffffffffffffff808211156116fc575f80fd5b61170889838a0161153c565b90965094506040880135915080821115611720575f80fd5b5061172d8882890161153c565b969995985093965092949392505050565b848152836020820152608060408201525f61175c60808301856115d5565b828103606084015261176e8185611623565b979650505050505050565b5f805f6060848603121561178b575f80fd5b83359250602084013561179d81611528565b929592945050506040919091013590565b5f602082840312156117be575f80fd5b81356117c981611528565b9392505050565b5f80604083850312156117e1575f80fd5b82356117ec81611528565b915060208301356116b681611528565b5f806040838503121561180d575f80fd5b823561181881611528565b946020939093013593505050565b5f8060408385031215611837575f80fd5b50508035926020909101359150565b8183525f60208085019450825f5b858110156116185781358752828201358388015260409687019690910190600101611854565b6001600160a01b0384168152604060208201525f61168a604083018486611846565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff811182821017156118d3576118d361189c565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156119025761190261189c565b604052919050565b5f67ffffffffffffffff8211156119235761192361189c565b5060051b60200190565b5f82601f83011261193c575f80fd5b8151602061195161194c8361190a565b6118d9565b82815260069290921b8401810191818101908684111561196f575f80fd5b8286015b848110156119ac576040818903121561198a575f80fd5b6119926118b0565b815181528482015185820152835291830191604001611973565b509695505050505050565b5f82601f8301126119c6575f80fd5b815160206119d661194c8361190a565b82815260069290921b840181019181810190868411156119f4575f80fd5b8286015b848110156119ac5760408189031215611a0f575f80fd5b611a176118b0565b8151611a2281611528565b815281850151858201528352918301916040016119f8565b5f8060408385031215611a4b575f80fd5b825167ffffffffffffffff80821115611a62575f80fd5b611a6e8683870161192d565b93506020850151915080821115611a83575f80fd5b50611a90858286016119b7565b9150509250929050565b5f6001600160a01b038088168352602060606020850152611abf60608501888a611846565b84810360408681019190915286825287916020015f5b88811015611b04578335611ae881611528565b8616825283850135858301529282019290820190600101611ad5565b509b9a5050505050505050505050565b5f805f8060808587031215611b27575f80fd5b8451935060208501519250604085015167ffffffffffffffff80821115611b4c575f80fd5b611b588883890161192d565b93506060870151915080821115611b6d575f80fd5b50611b7a878288016119b7565b91505092959194509250565b5f60208284031215611b96575f80fd5b81516117c981611528565b8082018082111561098757634e487b7160e01b5f52601160045260245ffd5b815181526020808301519082015260408101610987565b5f60208284031215611be7575f80fd5b5051919050565b5f805f60608486031215611c00575f80fd5b83519250602084015191506040840151905092509250925600000000000000000000000063c89f830de9b4b346be38d4cd6830cd2e3de68c000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000d2f13f7789f0000
Deployed Bytecode
0x608060405234801561000f575f80fd5b50600436106101c6575f3560e01c8063715018a6116100fe5780639ed1a9851161009e578063d95906551161006e578063d959065514610471578063f2fde38b1461049f578063f433262f146104b2578063f43c4eb4146104ba575f80fd5b80639ed1a985146103dd578063a91ee0dc146103f0578063aed019b914610403578063b997454f1461042b575f80fd5b80637b103999116100d95780637b1039991461036a578063853cb6ba1461037d5780638da5cb5b146103ba5780639630b472146103ca575f80fd5b8063715018a61461033c5780637512370014610344578063766121e214610357575f80fd5b80633a12c6da116101695780634623c81e116101445780634623c81e146102f95780634d1ccac5146103035780635c38eb3a14610316578063657df9d914610329575f80fd5b80633a12c6da146102d157806343862a2c146102da57806344148a92146102ef575f80fd5b80632794bc44116101a45780632794bc4414610236578063290c0409146102605780633483cbb2146102835780633617dfac146102aa575f80fd5b806304ab80f5146101ca5780630e71b464146101f457806316f0115b1461020b575b5f80fd5b6101dd6101d8366004611584565b6104cd565b6040516101eb929190611666565b60405180910390f35b6101fd60015481565b6040519081526020016101eb565b60045461021e906001600160a01b031681565b6040516001600160a01b0390911681526020016101eb565b6101fd610244366004611693565b600760209081525f928352604080842090915290825290205481565b61027361026e3660046116c1565b61056c565b6040516101eb949392919061173e565b6101fd7f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972881565b6101fd7f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7781565b6101fd60025481565b6102ed6102e8366004611779565b61061a565b005b6101fd6203f48081565b6101fd6201518081565b6101fd6103113660046117ae565b610902565b6102ed6103243660046117d0565b61098d565b6102ed610337366004611693565b6109fb565b6102ed610af6565b6101fd6103523660046117fc565b610b09565b6102ed610365366004611826565b610beb565b60035461021e906001600160a01b031681565b6103aa61038b366004611826565b600960209081525f928352604080842090915290825290205460ff1681565b60405190151581526020016101eb565b5f546001600160a01b031661021e565b6102ed6103d8366004611826565b610d9f565b60055461021e906001600160a01b031681565b6102ed6103fe3660046117ae565b610eab565b61021e6104113660046117ae565b60066020525f90815260409020546001600160a01b031681565b61045c610439366004611693565b600860209081525f92835260408084209091529082529020805460019091015482565b604080519283526020830191909152016101eb565b61048461047f3660046117ae565b610f07565b604080519384526020840192909252908201526060016101eb565b6102ed6104ad3660046117ae565b610f9f565b6102ed61102f565b6102ed6104c8366004611693565b6111ee565b6005546040517f04ab80f500000000000000000000000000000000000000000000000000000000815260609182916001600160a01b03909116906304ab80f59061051f9088908890889060040161187a565b5f60405180830381865afa158015610539573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526105609190810190611a3a565b91509150935093915050565b6005546040517f290c04090000000000000000000000000000000000000000000000000000000081525f91829160609182916001600160a01b039091169063290c0409906105c6908c908c908c908c908c90600401611a9a565b5f60405180830381865afa1580156105e0573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526106079190810190611b14565b929c919b50995090975095505050505050565b600480546040516331a9108f60e11b81529182018590526001600160a01b031690636352211e90602401602060405180830381865afa15801561065f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106839190611b86565b6001600160a01b0316336001600160a01b0316146106c25760405163fa5947cd60e01b8152600481018490523360248201526044015b60405180910390fd5b6001600160a01b038281165f908152600660205260409020541661070457604051630320697360e01b81526001600160a01b03831660048201526024016106b9565b600154811080610715575060025481115b1561074f576040517fc3f8e7ec000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b600480546040517f2307b4f90000000000000000000000000000000000000000000000000000000081529182018590526001600160a01b0384811692911690632307b4f990602401602060405180830381865afa1580156107b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190611b86565b6001600160a01b031603610819576040517f2e762e16000000000000000000000000000000000000000000000000000000008152600481018490526024016106b9565b6040805180820182525f8082526020808301829052868252600781528382206001600160a01b038716835290529182205490910361086a57506040805180820190915281815242602082015261088f565b6040518060400160405280838152602001620151804261088a9190611ba1565b905290505b5f8481526008602090815260408083206001600160a01b0387168085529083529281902084518155918401516001909201919091555185907f7cec4c2dc59d75545a22de1e92bdb560e111db5a849d19a5fa3383d86fde1584906108f4908590611bc0565b60405180910390a350505050565b6005546040517f4d1ccac50000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f921690634d1ccac590602401602060405180830381865afa158015610963573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109879190611bd7565b92915050565b610995611480565b6001600160a01b038281165f8181526006602090815260409182902080546001600160a01b0319169486169485179055905192835290917fc1d3048301c0d23629a2532c8defa6d68f8e1a0e4157918769e9fb1b2eeb888e910160405180910390a25050565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015610a40573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a649190611b86565b6001600160a01b0316336001600160a01b031614610a9e5760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b5f8281526008602090815260408083206001600160a01b038516808552925280832083815560010183905551909184917f8fc28ba5f3d7b339e7c15bafa3559ffda2a8828893f76efe63258e587062999c9190a35050565b610afe611480565b610b075f6114d9565b565b5f815f03610b1857505f610987565b6001600160a01b038084165f908152600660205260409020541680610b5b57604051630320697360e01b81526001600160a01b03851660048201526024016106b9565b6040517f751237000000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015260248201859052821690637512370090604401602060405180830381865afa158015610bbf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610be39190611bd7565b949350505050565b600480546040516331a9108f60e11b815291820184905233916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610c34573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c589190611b86565b6001600160a01b031614610c885760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b600480546040516331a9108f60e11b81529182018390525f916001600160a01b0390911690636352211e90602401602060405180830381865afa158015610cd1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cf59190611b86565b6001600160a01b031603610d38576040517f394d8c7e000000000000000000000000000000000000000000000000000000008152600481018290526024016106b9565b5f828152600960209081526040808320848452825291829020805460ff8082161560ff1990921682179092559251921615158252829184917f055ceb561d17c4f01875b36bf90963284328fbdec241334d73a5a18d3785c5e4910160405180910390a35050565b610da7611480565b815f03610de0576040517fe2dc5a7b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b670de0b6b3a76400008110610e21576040517f2b3af9e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808210610e64576040517f2392db5400000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016106b9565b6001829055600281905560408051838152602081018390527f76ddd1287961c754a16c5b15eb241d749a61e410e14fabb9e268f58145a3b537910160405180910390a15050565b610eb3611480565b600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b9060200160405180910390a150565b6005546040517fd95906550000000000000000000000000000000000000000000000000000000081526001600160a01b0383811660048301525f92839283929091169063d959065590602401606060405180830381865afa158015610f6e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f929190611bee565b9250925092509193909250565b610fa7611480565b6001600160a01b0381166110235760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106b9565b61102c816114d9565b50565b6003546040516318dee17b60e01b81527f1a99cbf6006db18a0e08427ff11db78f3ea1054bc5b9d48122aae8d206c0972860048201526001600160a01b03909116906318dee17b90602401602060405180830381865afa158015611095573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110b99190611b86565b600480546001600160a01b0319166001600160a01b039283161781556003546040516318dee17b60e01b81527f881469d14b8443f6c918bdd0a641e9d7cae2592dc28a4f922a2c4d7ca3d19c7792810192909252909116906318dee17b90602401602060405180830381865afa158015611135573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111599190611b86565b600580546001600160a01b0319166001600160a01b03928316179055600454604051911681527f025f89b99c8ce32af8da7624f4575b920a86ebf07870d85a9fb545fee349ddce9060200160405180910390a16005546040516001600160a01b0390911681527f808fc0874768c225409d9bf87fc94b6cdc40cb955dd7960727704b534b352b9b9060200160405180910390a1565b600480546040516331a9108f60e11b81529182018490526001600160a01b031690636352211e90602401602060405180830381865afa158015611233573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112579190611b86565b6001600160a01b0316336001600160a01b0316146112915760405163fa5947cd60e01b8152600481018390523360248201526044016106b9565b6001600160a01b038181165f90815260066020526040902054166112d357604051630320697360e01b81526001600160a01b03821660048201526024016106b9565b5f8281526008602090815260408083206001600160a01b03851684528252808320815180830190925280548252600101549181018290529103611354576040517f188505a6000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b42816020015111156113a4576040517f03c287a9000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b6203f48081602001516113b79190611ba1565b421115611402576040517f24aad6d1000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03831660248201526044016106b9565b80515f8481526007602090815260408083206001600160a01b03871680855290835281842094909455868352600882528083208484528252808320838155600101929092558351915191825285917ff0a6a76c322607a7ac64afb307da5dde6761948aa16f09f9bb66e7ac90f51e46910160405180910390a3505050565b5f546001600160a01b03163314610b075760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106b9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461102c575f80fd5b5f8083601f84011261154c575f80fd5b50813567ffffffffffffffff811115611563575f80fd5b6020830191508360208260061b850101111561157d575f80fd5b9250929050565b5f805f60408486031215611596575f80fd5b83356115a181611528565b9250602084013567ffffffffffffffff8111156115bc575f80fd5b6115c88682870161153c565b9497909650939450505050565b5f815180845260208085019450602084015f5b838110156116185761160587835180518252602090810151910152565b60409690960195908201906001016115e8565b509495945050505050565b5f815180845260208085019450602084015f5b8381101561161857815180516001600160a01b031688528301518388015260409096019590820190600101611636565b604081525f61167860408301856115d5565b828103602084015261168a8185611623565b95945050505050565b5f80604083850312156116a4575f80fd5b8235915060208301356116b681611528565b809150509250929050565b5f805f805f606086880312156116d5575f80fd5b85356116e081611528565b9450602086013567ffffffffffffffff808211156116fc575f80fd5b61170889838a0161153c565b90965094506040880135915080821115611720575f80fd5b5061172d8882890161153c565b969995985093965092949392505050565b848152836020820152608060408201525f61175c60808301856115d5565b828103606084015261176e8185611623565b979650505050505050565b5f805f6060848603121561178b575f80fd5b83359250602084013561179d81611528565b929592945050506040919091013590565b5f602082840312156117be575f80fd5b81356117c981611528565b9392505050565b5f80604083850312156117e1575f80fd5b82356117ec81611528565b915060208301356116b681611528565b5f806040838503121561180d575f80fd5b823561181881611528565b946020939093013593505050565b5f8060408385031215611837575f80fd5b50508035926020909101359150565b8183525f60208085019450825f5b858110156116185781358752828201358388015260409687019690910190600101611854565b6001600160a01b0384168152604060208201525f61168a604083018486611846565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff811182821017156118d3576118d361189c565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156119025761190261189c565b604052919050565b5f67ffffffffffffffff8211156119235761192361189c565b5060051b60200190565b5f82601f83011261193c575f80fd5b8151602061195161194c8361190a565b6118d9565b82815260069290921b8401810191818101908684111561196f575f80fd5b8286015b848110156119ac576040818903121561198a575f80fd5b6119926118b0565b815181528482015185820152835291830191604001611973565b509695505050505050565b5f82601f8301126119c6575f80fd5b815160206119d661194c8361190a565b82815260069290921b840181019181810190868411156119f4575f80fd5b8286015b848110156119ac5760408189031215611a0f575f80fd5b611a176118b0565b8151611a2281611528565b815281850151858201528352918301916040016119f8565b5f8060408385031215611a4b575f80fd5b825167ffffffffffffffff80821115611a62575f80fd5b611a6e8683870161192d565b93506020850151915080821115611a83575f80fd5b50611a90858286016119b7565b9150509250929050565b5f6001600160a01b038088168352602060606020850152611abf60608501888a611846565b84810360408681019190915286825287916020015f5b88811015611b04578335611ae881611528565b8616825283850135858301529282019290820190600101611ad5565b509b9a5050505050505050505050565b5f805f8060808587031215611b27575f80fd5b8451935060208501519250604085015167ffffffffffffffff80821115611b4c575f80fd5b611b588883890161192d565b93506060870151915080821115611b6d575f80fd5b50611b7a878288016119b7565b91505092959194509250565b5f60208284031215611b96575f80fd5b81516117c981611528565b8082018082111561098757634e487b7160e01b5f52601160045260245ffd5b815181526020808301519082015260408101610987565b5f60208284031215611be7575f80fd5b5051919050565b5f805f60608486031215611c00575f80fd5b835192506020840151915060408401519050925092509256
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.