Token
USDT / WBTC / WETH (tricryptov2)
ERC-20
Overview
Max Total Supply
11,319.05 tricryptov2
Holders
92
Total Transfers
-
Market
Onchain Market Cap
$0.00
Circulating Supply Market Cap
-
Other Info
Token Contract (WITH 18 Decimals)
Loading...
Loading
Loading...
Loading
Loading...
Loading
# | Exchange | Pair | Price | 24H Volume | % Volume |
---|
This contract contains unverified libraries: __CACHE_BREAKER__
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x32e59464...9EeF06e04 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
ERC20Test
Compiler Version
v0.8.23+commit.f704f362
Optimization Enabled:
Yes with 200 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; contract ERC20Test is ERC20Permit { constructor() ERC20("ERC Test", "TST") ERC20Permit("ERC Test") { } uint8 private DECIMALS = 18; function mint(address _addr, uint256 _amount) public { _mint(_addr, _amount); } function transferFrom( address sender, address recipient, uint256 amount ) public override returns (bool) { _transfer(sender, recipient, amount); return true; } function decimals() public view override returns (uint8) { return DECIMALS; } function setDecimals(uint8 _decimals) public { DECIMALS = _decimals; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); function getRoundData( uint80 _roundId ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) pragma solidity ^0.8.20; import {ContextUpgradeable} from "../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. * * The initial owner is set to the address provided by the deployer. 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 { /// @custom:storage-location erc7201:openzeppelin.storage.Ownable struct OwnableStorage { address _owner; } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300; function _getOwnableStorage() private pure returns (OwnableStorage storage $) { assembly { $.slot := OwnableStorageLocation } } /** * @dev The caller account is not authorized to perform an operation. */ error OwnableUnauthorizedAccount(address account); /** * @dev The owner is not a valid owner account. (eg. `address(0)`) */ error OwnableInvalidOwner(address owner); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the address provided by the deployer as the initial owner. */ function __Ownable_init(address initialOwner) internal onlyInitializing { __Ownable_init_unchained(initialOwner); } function __Ownable_init_unchained(address initialOwner) internal onlyInitializing { if (initialOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(initialOwner); } /** * @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) { OwnableStorage storage $ = _getOwnableStorage(); return $._owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { if (owner() != _msgSender()) { revert OwnableUnauthorizedAccount(_msgSender()); } } /** * @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 { if (newOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { OwnableStorage storage $ = _getOwnableStorage(); address oldOwner = $._owner; $._owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.20; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in * case an upgrade adds a module that needs to be initialized. * * For example: * * [.hljs-theme-light.nopadding] * ```solidity * contract MyToken is ERC20Upgradeable { * function initialize() initializer public { * __ERC20_init("MyToken", "MTK"); * } * } * * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { * function initializeV2() reinitializer(2) public { * __ERC20Permit_init("MyToken"); * } * } * ``` * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() { * _disableInitializers(); * } * ``` * ==== */ abstract contract Initializable { /** * @dev Storage of the initializable contract. * * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions * when using with upgradeable contracts. * * @custom:storage-location erc7201:openzeppelin.storage.Initializable */ struct InitializableStorage { /** * @dev Indicates that the contract has been initialized. */ uint64 _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool _initializing; } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; /** * @dev The contract is already initialized. */ error InvalidInitialization(); /** * @dev The contract is not initializing. */ error NotInitializing(); /** * @dev Triggered when the contract has been initialized or reinitialized. */ event Initialized(uint64 version); /** * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, * `onlyInitializing` functions can be used to initialize parent contracts. * * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in * production. * * Emits an {Initialized} event. */ modifier initializer() { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); // Cache values to avoid duplicated sloads bool isTopLevelCall = !$._initializing; uint64 initialized = $._initialized; // Allowed calls: // - initialSetup: the contract is not in the initializing state and no previous version was // initialized // - construction: the contract is initialized at version 1 (no reininitialization) and the // current contract is just being deployed bool initialSetup = initialized == 0 && isTopLevelCall; bool construction = initialized == 1 && address(this).code.length == 0; if (!initialSetup && !construction) { revert InvalidInitialization(); } $._initialized = 1; if (isTopLevelCall) { $._initializing = true; } _; if (isTopLevelCall) { $._initializing = false; emit Initialized(1); } } /** * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be * used to initialize parent contracts. * * A reinitializer may be used after the original initialization step. This is essential to configure modules that * are added through upgrades and that require initialization. * * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` * cannot be nested. If one is invoked in the context of another, execution will revert. * * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in * a contract, executing them in the right order is up to the developer or operator. * * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. * * Emits an {Initialized} event. */ modifier reinitializer(uint64 version) { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); if ($._initializing || $._initialized >= version) { revert InvalidInitialization(); } $._initialized = version; $._initializing = true; _; $._initializing = false; emit Initialized(version); } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { _checkInitializing(); _; } /** * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. */ function _checkInitializing() internal view virtual { if (!_isInitializing()) { revert NotInitializing(); } } /** * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized * to any version. It is recommended to use this to lock implementation contracts that are designed to be called * through proxies. * * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); if ($._initializing) { revert InvalidInitialization(); } if ($._initialized != type(uint64).max) { $._initialized = type(uint64).max; emit Initialized(type(uint64).max); } } /** * @dev Returns the highest version that has been initialized. See {reinitializer}. */ function _getInitializedVersion() internal view returns (uint64) { return _getInitializableStorage()._initialized; } /** * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. */ function _isInitializing() internal view returns (bool) { return _getInitializableStorage()._initializing; } /** * @dev Returns a pointer to the storage namespace. */ // solhint-disable-next-line var-name-mixedcase function _getInitializableStorage() private pure returns (InitializableStorage storage $) { assembly { $.slot := INITIALIZABLE_STORAGE } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol) pragma solidity ^0.8.20; import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {Initializable} from "./Initializable.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. * * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. */ abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable __self = address(this); /** * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)` * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string. * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function * during an upgrade. */ string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; /** * @dev The call is from an unauthorized context. */ error UUPSUnauthorizedCallContext(); /** * @dev The storage `slot` is unsupported as a UUID. */ error UUPSUnsupportedProxiableUUID(bytes32 slot); /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to * fail. */ modifier onlyProxy() { _checkProxy(); _; } /** * @dev Check that the execution is not being performed through a delegate call. This allows a function to be * callable on the implementing contract but not through proxies. */ modifier notDelegated() { _checkNotDelegated(); _; } function __UUPSUpgradeable_init() internal onlyInitializing { } function __UUPSUpgradeable_init_unchained() internal onlyInitializing { } /** * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the * implementation. It is used to validate the implementation's compatibility when performing an upgrade. * * 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. This is guaranteed by the `notDelegated` modifier. */ function proxiableUUID() external view virtual notDelegated returns (bytes32) { return ERC1967Utils.IMPLEMENTATION_SLOT; } /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. * * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data); } /** * @dev Reverts if the execution is not performed via delegatecall or the execution * context is not of a proxy with an ERC1967-compliant implementation pointing to self. * See {_onlyProxy}. */ function _checkProxy() internal view virtual { if ( address(this) == __self || // Must be called through delegatecall ERC1967Utils.getImplementation() != __self // Must be called through an active proxy ) { revert UUPSUnauthorizedCallContext(); } } /** * @dev Reverts if the execution is performed via delegatecall. * See {notDelegated}. */ function _checkNotDelegated() internal view virtual { if (address(this) != __self) { // Must not be called through delegatecall revert UUPSUnauthorizedCallContext(); } } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity * function _authorizeUpgrade(address) internal onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; /** * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call. * * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value * is expected to be the implementation slot in ERC1967. * * Emits an {IERC1967-Upgraded} event. */ function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) { revert UUPSUnsupportedProxiableUUID(slot); } ERC1967Utils.upgradeToAndCall(newImplementation, data); } catch { // The implementation is not UUPS revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; 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; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol) pragma solidity ^0.8.20; import {ContextUpgradeable} from "../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 { /// @custom:storage-location erc7201:openzeppelin.storage.Pausable struct PausableStorage { bool _paused; } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300; function _getPausableStorage() private pure returns (PausableStorage storage $) { assembly { $.slot := PausableStorageLocation } } /** * @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); /** * @dev The operation failed because the contract is paused. */ error EnforcedPause(); /** * @dev The operation failed because the contract is not paused. */ error ExpectedPause(); /** * @dev Initializes the contract in unpaused state. */ function __Pausable_init() internal onlyInitializing { __Pausable_init_unchained(); } function __Pausable_init_unchained() internal onlyInitializing { PausableStorage storage $ = _getPausableStorage(); $._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) { PausableStorage storage $ = _getPausableStorage(); return $._paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { if (paused()) { revert EnforcedPause(); } } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { if (!paused()) { revert ExpectedPause(); } } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { PausableStorage storage $ = _getPausableStorage(); $._paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { PausableStorage storage $ = _getPausableStorage(); $._paused = false; emit Unpaused(_msgSender()); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; 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; /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard struct ReentrancyGuardStorage { uint256 _status; } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00; function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) { assembly { $.slot := ReentrancyGuardStorageLocation } } /** * @dev Unauthorized reentrant call. */ error ReentrancyGuardReentrantCall(); function __ReentrancyGuard_init() internal onlyInitializing { __ReentrancyGuard_init_unchained(); } function __ReentrancyGuard_init_unchained() internal onlyInitializing { ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); $._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 { ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); // On the first call to nonReentrant, _status will be NOT_ENTERED if ($._status == ENTERED) { revert ReentrancyGuardReentrantCall(); } // Any calls to nonReentrant after this point will fail $._status = ENTERED; } function _nonReentrantAfter() private { ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); // 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) { ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); return $._status == ENTERED; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) pragma solidity ^0.8.20; import {Context} from "../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. * * The initial owner is set to the address provided by the deployer. 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; /** * @dev The caller account is not authorized to perform an operation. */ error OwnableUnauthorizedAccount(address account); /** * @dev The owner is not a valid owner account. (eg. `address(0)`) */ error OwnableInvalidOwner(address owner); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the address provided by the deployer as the initial owner. */ constructor(address initialOwner) { if (initialOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(initialOwner); } /** * @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 { if (owner() != _msgSender()) { revert OwnableUnauthorizedAccount(_msgSender()); } } /** * @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 { if (newOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _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 v5.0.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.20; /** * @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 v5.0.0) (interfaces/draft-IERC6093.sol) pragma solidity ^0.8.20; /** * @dev Standard ERC20 Errors * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. */ interface IERC20Errors { /** * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. * @param balance Current balance for the interacting account. * @param needed Minimum amount required to perform a transfer. */ error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC20InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC20InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. * @param spender Address that may be allowed to operate on tokens without being their owner. * @param allowance Amount of tokens a `spender` is allowed to operate with. * @param needed Minimum amount required to perform a transfer. */ error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC20InvalidApprover(address approver); /** * @dev Indicates a failure with the `spender` to be approved. Used in approvals. * @param spender Address that may be allowed to operate on tokens without being their owner. */ error ERC20InvalidSpender(address spender); } /** * @dev Standard ERC721 Errors * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. */ interface IERC721Errors { /** * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. * Used in balance queries. * @param owner Address of the current owner of a token. */ error ERC721InvalidOwner(address owner); /** * @dev Indicates a `tokenId` whose `owner` is the zero address. * @param tokenId Identifier number of a token. */ error ERC721NonexistentToken(uint256 tokenId); /** * @dev Indicates an error related to the ownership over a particular token. Used in transfers. * @param sender Address whose tokens are being transferred. * @param tokenId Identifier number of a token. * @param owner Address of the current owner of a token. */ error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC721InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC721InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `operator`’s approval. Used in transfers. * @param operator Address that may be allowed to operate on tokens without being their owner. * @param tokenId Identifier number of a token. */ error ERC721InsufficientApproval(address operator, uint256 tokenId); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC721InvalidApprover(address approver); /** * @dev Indicates a failure with the `operator` to be approved. Used in approvals. * @param operator Address that may be allowed to operate on tokens without being their owner. */ error ERC721InvalidOperator(address operator); } /** * @dev Standard ERC1155 Errors * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. */ interface IERC1155Errors { /** * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. * @param balance Current balance for the interacting account. * @param needed Minimum amount required to perform a transfer. * @param tokenId Identifier number of a token. */ error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC1155InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC1155InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `operator`’s approval. Used in transfers. * @param operator Address that may be allowed to operate on tokens without being their owner. * @param owner Address of the current owner of a token. */ error ERC1155MissingApprovalForAll(address operator, address owner); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC1155InvalidApprover(address approver); /** * @dev Indicates a failure with the `operator` to be approved. Used in approvals. * @param operator Address that may be allowed to operate on tokens without being their owner. */ error ERC1155InvalidOperator(address operator); /** * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. * Used in batch transfers. * @param idsLength Length of the array of token identifiers * @param valuesLength Length of the array of token amounts */ error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol) pragma solidity ^0.8.20; interface IERC5267 { /** * @dev MAY be emitted to signal that the domain could have changed. */ event EIP712DomainChanged(); /** * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 * signature. */ function eip712Domain() external view returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol) pragma solidity ^0.8.20; /** * @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. * * {UpgradeableBeacon} will check that this address is a contract. */ function implementation() external view returns (address); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol) pragma solidity ^0.8.20; import {IBeacon} from "../beacon/IBeacon.sol"; import {Address} from "../../utils/Address.sol"; import {StorageSlot} from "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. */ library ERC1967Utils { // We re-declare ERC-1967 events here because they can't be used directly from IERC1967. // This will be fixed in Solidity 0.8.21. At that point we should remove these events. /** * @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); /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev The `implementation` of the proxy is invalid. */ error ERC1967InvalidImplementation(address implementation); /** * @dev The `admin` of the proxy is invalid. */ error ERC1967InvalidAdmin(address admin); /** * @dev The `beacon` of the proxy is invalid. */ error ERC1967InvalidBeacon(address beacon); /** * @dev An upgrade function sees `msg.value > 0` that may be lost. */ error ERC1967NonPayable(); /** * @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 { if (newImplementation.code.length == 0) { revert ERC1967InvalidImplementation(newImplementation); } StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Performs implementation upgrade with additional setup call if data is nonempty. * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected * to avoid stuck value in the contract. * * Emits an {IERC1967-Upgraded} event. */ function upgradeToAndCall(address newImplementation, bytes memory data) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); if (data.length > 0) { Address.functionDelegateCall(newImplementation, data); } else { _checkNonPayable(); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Returns the current admin. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ 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 { if (newAdmin == address(0)) { revert ERC1967InvalidAdmin(address(0)); } StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {IERC1967-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 the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore 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 { if (newBeacon.code.length == 0) { revert ERC1967InvalidBeacon(newBeacon); } StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; address beaconImplementation = IBeacon(newBeacon).implementation(); if (beaconImplementation.code.length == 0) { revert ERC1967InvalidImplementation(beaconImplementation); } } /** * @dev Change the beacon and trigger a setup call if data is nonempty. * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected * to avoid stuck value in the contract. * * Emits an {IERC1967-BeaconUpgraded} event. * * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for * efficiency. */ function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } else { _checkNonPayable(); } } /** * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract * if an upgrade doesn't perform an initialization call. */ function _checkNonPayable() private { if (msg.value > 0) { revert ERC1967NonPayable(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "./IERC20.sol"; import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; import {Context} from "../../utils/Context.sol"; import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * * TIP: For a detailed writeup see our guide * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * The default value of {decimals} is 18. To change this, you should override * this function so it returns a different value. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. */ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { mapping(address account => uint256) private _balances; mapping(address account => mapping(address spender => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * All two of these values are immutable: they can only be set once during * construction. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the default value returned by this function, unless * it's overridden. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `value`. */ function transfer(address to, uint256 value) public virtual returns (bool) { address owner = _msgSender(); _transfer(owner, to, value); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 value) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, value); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `value`. * - the caller must have allowance for ``from``'s tokens of at least * `value`. */ function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, value); _transfer(from, to, value); return true; } /** * @dev Moves a `value` amount of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer(address from, address to, uint256 value) internal { if (from == address(0)) { revert ERC20InvalidSender(address(0)); } if (to == address(0)) { revert ERC20InvalidReceiver(address(0)); } _update(from, to, value); } /** * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding * this function. * * Emits a {Transfer} event. */ function _update(address from, address to, uint256 value) internal virtual { if (from == address(0)) { // Overflow check required: The rest of the code assumes that totalSupply never overflows _totalSupply += value; } else { uint256 fromBalance = _balances[from]; if (fromBalance < value) { revert ERC20InsufficientBalance(from, fromBalance, value); } unchecked { // Overflow not possible: value <= fromBalance <= totalSupply. _balances[from] = fromBalance - value; } } if (to == address(0)) { unchecked { // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. _totalSupply -= value; } } else { unchecked { // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. _balances[to] += value; } } emit Transfer(from, to, value); } /** * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). * Relies on the `_update` mechanism * * Emits a {Transfer} event with `from` set to the zero address. * * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 value) internal { if (account == address(0)) { revert ERC20InvalidReceiver(address(0)); } _update(address(0), account, value); } /** * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. * Relies on the `_update` mechanism. * * Emits a {Transfer} event with `to` set to the zero address. * * NOTE: This function is not virtual, {_update} should be overridden instead */ function _burn(address account, uint256 value) internal { if (account == address(0)) { revert ERC20InvalidSender(address(0)); } _update(account, address(0), value); } /** * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. * * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. */ function _approve(address owner, address spender, uint256 value) internal { _approve(owner, spender, value, true); } /** * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. * * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any * `Approval` event during `transferFrom` operations. * * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to * true using the following override: * ``` * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { * super._approve(owner, spender, value, true); * } * ``` * * Requirements are the same as {_approve}. */ function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { if (owner == address(0)) { revert ERC20InvalidApprover(address(0)); } if (spender == address(0)) { revert ERC20InvalidSpender(address(0)); } _allowances[owner][spender] = value; if (emitEvent) { emit Approval(owner, spender, value); } } /** * @dev Updates `owner` s allowance for `spender` based on spent `value`. * * Does not update the allowance value in case of infinite allowance. * Revert if not enough allowance is available. * * Does not emit an {Approval} event. */ function _spendAllowance(address owner, address spender, uint256 value) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { if (currentAllowance < value) { revert ERC20InsufficientAllowance(spender, currentAllowance, value); } unchecked { _approve(owner, spender, currentAllowance - value, false); } } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol) pragma solidity ^0.8.20; import {IERC20Permit} from "./IERC20Permit.sol"; import {ERC20} from "../ERC20.sol"; import {ECDSA} from "../../../utils/cryptography/ECDSA.sol"; import {EIP712} from "../../../utils/cryptography/EIP712.sol"; import {Nonces} from "../../../utils/Nonces.sol"; /** * @dev Implementation 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. */ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { bytes32 private constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); /** * @dev Permit deadline has expired. */ error ERC2612ExpiredSignature(uint256 deadline); /** * @dev Mismatched signature. */ error ERC2612InvalidSigner(address signer, address owner); /** * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. * * It's a good idea to use the same `name` that is defined as the ERC20 token name. */ constructor(string memory name) EIP712(name, "1") {} /** * @inheritdoc IERC20Permit */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { if (block.timestamp > deadline) { revert ERC2612ExpiredSignature(deadline); } bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); if (signer != owner) { revert ERC2612InvalidSigner(signer, owner); } _approve(owner, spender, value); } /** * @inheritdoc IERC20Permit */ function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) { return super.nonces(owner); } /** * @inheritdoc IERC20Permit */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view virtual returns (bytes32) { return _domainSeparatorV4(); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. */ interface IERC20Metadata is IERC20 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.20; /** * @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 v5.0.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; /** * @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 value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 value) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 value) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC20Permit} from "../extensions/IERC20Permit.sol"; import {Address} from "../../../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 An operation with an ERC20 token failed. */ error SafeERC20FailedOperation(address token); /** * @dev Indicates a failed `decreaseAllowance` request. */ error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); /** * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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); forceApprove(token, spender, oldAllowance + value); } /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { uint256 currentAllowance = token.allowance(address(this), spender); if (currentAllowance < requestedDecrease) { revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); } forceApprove(token, spender, currentAllowance - requestedDecrease); } } /** * @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.encodeCall(token.approve, (spender, value)); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); _callOptionalReturn(token, approvalCall); } } /** * @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); if (returndata.length != 0 && !abi.decode(returndata, (bool))) { revert SafeERC20FailedOperation(address(token)); } } /** * @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(token).code.length > 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) pragma solidity ^0.8.20; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error AddressInsufficientBalance(address account); /** * @dev There's no code at `target` (it is not a contract). */ error AddressEmptyCode(address target); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedInnerCall(); /** * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { revert AddressInsufficientBalance(address(this)); } (bool success, ) = recipient.call{value: amount}(""); if (!success) { revert FailedInnerCall(); } } /** * @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 or custom error, it is bubbled * up by this function (like regular Solidity function calls). However, if * the call reverted with no returned reason, this function reverts with a * {FailedInnerCall} error. * * 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. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0); } /** * @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`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { if (address(this).balance < value) { revert AddressInsufficientBalance(address(this)); } (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an * unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata ) internal view returns (bytes memory) { if (!success) { _revert(returndata); } else { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract if (returndata.length == 0 && target.code.length == 0) { revert AddressEmptyCode(target); } return returndata; } } /** * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the * revert reason or with a default {FailedInnerCall} error. */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (!success) { _revert(returndata); } else { return returndata; } } /** * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. */ function _revert(bytes memory returndata) 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 FailedInnerCall(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; /** * @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 v5.0.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.20; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS } /** * @dev The signature derives the `address(0)`. */ error ECDSAInvalidSignature(); /** * @dev The signature has an invalid length. */ error ECDSAInvalidSignatureLength(uint256 length); /** * @dev The signature has an S value that is in the upper half order. */ error ECDSAInvalidSignatureS(bytes32 s); /** * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not * return address(0) without also returning an error description. Errors are documented using an enum (error type) * and a bytes32 providing additional information about the error. * * If no error is returned, then the address can be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. */ function tryRecover( bytes32 hash, uint8 v, bytes32 r, bytes32 s ) internal pure returns (address, RecoverError, bytes32) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS, s); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature, bytes32(0)); } return (signer, RecoverError.NoError, bytes32(0)); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); _throwError(error, errorArg); return recovered; } /** * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. */ function _throwError(RecoverError error, bytes32 errorArg) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert ECDSAInvalidSignature(); } else if (error == RecoverError.InvalidSignatureLength) { revert ECDSAInvalidSignatureLength(uint256(errorArg)); } else if (error == RecoverError.InvalidSignatureS) { revert ECDSAInvalidSignatureS(errorArg); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol) pragma solidity ^0.8.20; import {MessageHashUtils} from "./MessageHashUtils.sol"; import {ShortStrings, ShortString} from "../ShortStrings.sol"; import {IERC5267} from "../../interfaces/IERC5267.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. * * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. * * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA * ({_hashTypedDataV4}). * * The implementation of the domain separator was designed to be as efficient as possible while still properly updating * the chain id to protect against replay attacks on an eventual fork of the chain. * * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. * * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * * @custom:oz-upgrades-unsafe-allow state-variable-immutable */ abstract contract EIP712 is IERC5267 { using ShortStrings for *; bytes32 private constant TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to // invalidate the cached domain separator if the chain id changes. bytes32 private immutable _cachedDomainSeparator; uint256 private immutable _cachedChainId; address private immutable _cachedThis; bytes32 private immutable _hashedName; bytes32 private immutable _hashedVersion; ShortString private immutable _name; ShortString private immutable _version; string private _nameFallback; string private _versionFallback; /** * @dev Initializes the domain separator and parameter caches. * * The meaning of `name` and `version` is specified in * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: * * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. * - `version`: the current major version of the signing domain. * * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart * contract upgrade]. */ constructor(string memory name, string memory version) { _name = name.toShortStringWithFallback(_nameFallback); _version = version.toShortStringWithFallback(_versionFallback); _hashedName = keccak256(bytes(name)); _hashedVersion = keccak256(bytes(version)); _cachedChainId = block.chainid; _cachedDomainSeparator = _buildDomainSeparator(); _cachedThis = address(this); } /** * @dev Returns the domain separator for the current chain. */ function _domainSeparatorV4() internal view returns (bytes32) { if (address(this) == _cachedThis && block.chainid == _cachedChainId) { return _cachedDomainSeparator; } else { return _buildDomainSeparator(); } } function _buildDomainSeparator() private view returns (bytes32) { return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); } /** * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this * function returns the hash of the fully encoded EIP712 message for this domain. * * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: * * ```solidity * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( * keccak256("Mail(address to,string contents)"), * mailTo, * keccak256(bytes(mailContents)) * ))); * address signer = ECDSA.recover(digest, signature); * ``` */ function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); } /** * @dev See {IERC-5267}. */ function eip712Domain() public view virtual returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ) { return ( hex"0f", // 01111 _EIP712Name(), _EIP712Version(), block.chainid, address(this), bytes32(0), new uint256[](0) ); } /** * @dev The name parameter for the EIP712 domain. * * NOTE: By default this function reads _name which is an immutable value. * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). */ // solhint-disable-next-line func-name-mixedcase function _EIP712Name() internal view returns (string memory) { return _name.toStringWithFallback(_nameFallback); } /** * @dev The version parameter for the EIP712 domain. * * NOTE: By default this function reads _version which is an immutable value. * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). */ // solhint-disable-next-line func-name-mixedcase function _EIP712Version() internal view returns (string memory) { return _version.toStringWithFallback(_versionFallback); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) pragma solidity ^0.8.20; import {Strings} from "../Strings.sol"; /** * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. * * The library provides methods for generating a hash of a message that conforms to the * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] * specifications. */ library MessageHashUtils { /** * @dev Returns the keccak256 digest of an EIP-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing a bytes32 `messageHash` with * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with * keccak256, although any bytes32 value can be safely used because the final digest will * be re-hashed. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) } } /** * @dev Returns the keccak256 digest of an EIP-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing an arbitrary `message` with * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { return keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); } /** * @dev Returns the keccak256 digest of an EIP-191 signed data with version * `0x00` (data with intended validator). * * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended * `validator` address. Then hashing the result. * * See {ECDSA-recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked(hex"19_00", validator, data)); } /** * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`). * * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with * `\x19\x01` and hashing the result. It corresponds to the hash signed by the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. * * See {ECDSA-recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, hex"19_01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) digest := keccak256(ptr, 0x42) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { /** * @dev Muldiv operation overflow. */ error MathOverflowedMulDiv(); enum Rounding { Floor, // Toward negative infinity Ceil, // Toward positive infinity Trunc, // Toward zero Expand // Away from zero } /** * @dev Returns the addition of two unsigned integers, with an overflow flag. */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an overflow flag. */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @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 towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { // Guarantee the same behavior as in a regular Solidity division. return a / b; } // (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 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) 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. if (denominator <= prod1) { revert MathOverflowedMulDiv(); } /////////////////////////////////////////////// // 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. uint256 twos = denominator & (0 - denominator); 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 (unsignedRoundsUp(rounding) && 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 * towards zero. * * 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2 of a positive value rounded towards zero. * 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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10 of a positive value rounded towards zero. * 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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256 of a positive value rounded towards zero. * 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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); } } /** * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. */ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { return uint8(rounding) % 2 == 1; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.20; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol) pragma solidity ^0.8.20; /** * @dev Provides tracking nonces for addresses. Nonces will only increment. */ abstract contract Nonces { /** * @dev The nonce used for an `account` is not the expected current nonce. */ error InvalidAccountNonce(address account, uint256 currentNonce); mapping(address account => uint256) private _nonces; /** * @dev Returns the next unused nonce for an address. */ function nonces(address owner) public view virtual returns (uint256) { return _nonces[owner]; } /** * @dev Consumes a nonce. * * Returns the current value and increments nonce. */ function _useNonce(address owner) internal virtual returns (uint256) { // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be // decremented or reset. This guarantees that the nonce never overflows. unchecked { // It is important to do x++ and not ++x here. return _nonces[owner]++; } } /** * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. */ function _useCheckedNonce(address owner, uint256 nonce) internal virtual { uint256 current = _useNonce(owner); if (nonce != current) { revert InvalidAccountNonce(owner, current); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol) pragma solidity ^0.8.20; import {StorageSlot} from "./StorageSlot.sol"; // | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | // | length | 0x BB | type ShortString is bytes32; /** * @dev This library provides functions to convert short memory strings * into a `ShortString` type that can be used as an immutable variable. * * Strings of arbitrary length can be optimized using this library if * they are short enough (up to 31 bytes) by packing them with their * length (1 byte) in a single EVM word (32 bytes). Additionally, a * fallback mechanism can be used for every other case. * * Usage example: * * ```solidity * contract Named { * using ShortStrings for *; * * ShortString private immutable _name; * string private _nameFallback; * * constructor(string memory contractName) { * _name = contractName.toShortStringWithFallback(_nameFallback); * } * * function name() external view returns (string memory) { * return _name.toStringWithFallback(_nameFallback); * } * } * ``` */ library ShortStrings { // Used as an identifier for strings longer than 31 bytes. bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF; error StringTooLong(string str); error InvalidShortString(); /** * @dev Encode a string of at most 31 chars into a `ShortString`. * * This will trigger a `StringTooLong` error is the input string is too long. */ function toShortString(string memory str) internal pure returns (ShortString) { bytes memory bstr = bytes(str); if (bstr.length > 31) { revert StringTooLong(str); } return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length)); } /** * @dev Decode a `ShortString` back to a "normal" string. */ function toString(ShortString sstr) internal pure returns (string memory) { uint256 len = byteLength(sstr); // using `new string(len)` would work locally but is not memory safe. string memory str = new string(32); /// @solidity memory-safe-assembly assembly { mstore(str, len) mstore(add(str, 0x20), sstr) } return str; } /** * @dev Return the length of a `ShortString`. */ function byteLength(ShortString sstr) internal pure returns (uint256) { uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF; if (result > 31) { revert InvalidShortString(); } return result; } /** * @dev Encode a string into a `ShortString`, or write it to storage if it is too long. */ function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) { if (bytes(value).length < 32) { return toShortString(value); } else { StorageSlot.getStringSlot(store).value = value; return ShortString.wrap(FALLBACK_SENTINEL); } } /** * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. */ function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) { if (ShortString.unwrap(value) != FALLBACK_SENTINEL) { return toString(value); } else { return store; } } /** * @dev Return the length of a string that was encoded to `ShortString` or written to storage using * {setWithFallback}. * * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of * actual characters as the UTF-8 encoding of a single character can span over multiple bytes. */ function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) { if (ShortString.unwrap(value) != FALLBACK_SENTINEL) { return byteLength(value); } else { return bytes(store).length; } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.20; /** * @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(newImplementation.code.length > 0); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` */ 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 v5.0.0) (utils/Strings.sol) pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { bytes16 private constant HEX_DIGITS = "0123456789abcdef"; uint8 private constant ADDRESS_LENGTH = 20; /** * @dev The `value` string doesn't fit in the specified `length`. */ error StringsInsufficientHexLength(uint256 value, uint256 length); /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toStringSigned(int256 value) internal pure returns (string memory) { return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, Math.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { uint256 localValue = value; bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = HEX_DIGITS[localValue & 0xf]; localValue >>= 4; } if (localValue != 0) { revert StringsInsufficientHexLength(value, length); } return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal * representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { SafetyTransfer } from "./Dependencies/SafetyTransfer.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { IActivePool } from "./Interfaces/IActivePool.sol"; import { IDeposit } from "./Interfaces/IDeposit.sol"; /* * The Active Pool holds the collaterals and debt amounts for all active trenBoxes. * * When a trenBox is liquidated, it's collateral and debt tokens are transferred from the Active Pool, to either the * Stability Pool, the Default Pool, or both, depending on the liquidation conditions. * */ contract ActivePool is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable, IActivePool, IDeposit, ConfigurableAddresses { using SafeERC20 for IERC20; string public constant NAME = "ActivePool"; mapping(address collateral => uint256 balance) internal collateralBalances; mapping(address collateral => uint256 balance) internal debtTokenBalances; modifier onlyBorroweOperationsOrDefaultPool() { if (msg.sender != borrowerOperations && msg.sender != defaultPool) { revert ActivePool__NotAuthorizedContract(); } _; } modifier onlyBorrowerOperationsOrTrenBoxManager() { if (msg.sender != borrowerOperations && msg.sender != trenBoxManager) { revert ActivePool__NotAuthorizedContract(); } _; } modifier callerIsBorrowerOpsOrStabilityPoolOrTrenBoxMgr() { if ( msg.sender != borrowerOperations && msg.sender != stabilityPool && msg.sender != trenBoxManager ) { revert ActivePool__NotAuthorizedContract(); } _; } modifier onlyAuthorizedProtocolContracts() { if ( msg.sender != borrowerOperations && msg.sender != stabilityPool && msg.sender != trenBoxManager && msg.sender != trenBoxManagerOperations ) { revert ActivePool__NotAuthorizedContract(); } _; } function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); } function getAssetBalance(address _asset) external view override returns (uint256) { return collateralBalances[_asset]; } function getDebtTokenBalance(address _asset) external view override returns (uint256) { return debtTokenBalances[_asset]; } function increaseDebt( address _collateral, uint256 _amount ) external override onlyBorrowerOperationsOrTrenBoxManager { uint256 newDebt = debtTokenBalances[_collateral] + _amount; debtTokenBalances[_collateral] = newDebt; emit ActivePoolDebtUpdated(_collateral, newDebt); } function decreaseDebt( address _asset, uint256 _amount ) external override callerIsBorrowerOpsOrStabilityPoolOrTrenBoxMgr { uint256 newDebt = debtTokenBalances[_asset] - _amount; debtTokenBalances[_asset] = newDebt; emit ActivePoolDebtUpdated(_asset, newDebt); } // --- Pool functionality --- function sendAsset( address _asset, address _account, uint256 _amount ) external override nonReentrant onlyAuthorizedProtocolContracts { uint256 safetyTransferAmount = SafetyTransfer.decimalsCorrection(_asset, _amount); if (safetyTransferAmount == 0) return; uint256 newBalance = collateralBalances[_asset] - _amount; collateralBalances[_asset] = newBalance; IERC20(_asset).safeTransfer(_account, safetyTransferAmount); if (isERC20DepositContract(_account)) { IDeposit(_account).receivedERC20(_asset, _amount); } emit ActivePoolAssetBalanceUpdated(_asset, newBalance); emit AssetSent(_account, _asset, safetyTransferAmount); } function receivedERC20( address _asset, uint256 _amount ) external onlyBorroweOperationsOrDefaultPool { uint256 newBalance = collateralBalances[_asset] + _amount; collateralBalances[_asset] = newBalance; emit ActivePoolAssetBalanceUpdated(_asset, newBalance); } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } function isERC20DepositContract(address _account) private view returns (bool) { return (_account == defaultPool || _account == collSurplusPool || _account == stabilityPool); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { DECIMAL_PRECISION as _DECIMAL_PRECISION } from "./Dependencies/TrenMath.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; import { IStabilityPool } from "./Interfaces/IStabilityPool.sol"; import { IActivePool } from "./Interfaces/IActivePool.sol"; import { IDefaultPool } from "./Interfaces/IDefaultPool.sol"; contract AdminContract is IAdminContract, UUPSUpgradeable, OwnableUpgradeable, ConfigurableAddresses { // Constants // -------------------------------------------------------------------------------------------------------- string public constant NAME = "AdminContract"; uint256 public constant _100pct = 1 ether; // 1e18 == 100% uint256 public constant BORROWING_FEE_DEFAULT = 0.005 ether; // 0.5% uint256 public constant CCR_DEFAULT = 1.5 ether; // 150% uint256 public constant MCR_DEFAULT = 1.1 ether; // 110% uint256 public constant MIN_NET_DEBT_DEFAULT = 2000 ether; uint256 public constant MINT_CAP_DEFAULT = 1_000_000 ether; // 1 million GRAI uint256 public constant PERCENT_DIVISOR_DEFAULT = 200; // dividing by 200 yields 0.5% uint256 public constant REDEMPTION_FEE_FLOOR_DEFAULT = 0.005 ether; // 0.5% uint256 public constant REDEMPTION_BLOCK_TIMESTAMP_DEFAULT = type(uint256).max; // never // State // ------------------------------------------------------------------------------------------------------------ /** * @dev Cannot be public as struct has too many variables for the stack. * @dev Create special view structs/getters instead. */ mapping(address collateral => CollateralParams params) internal collateralParams; FlashLoanParams public flashLoanParams; // list of all collateral types in collateralParams (active and deprecated) // Addresses for easy access address[] public validCollateral; // index maps to token address. bool public isSetupInitialized; bool public routeToTRENStaking = false; // if true, collected fees go to stakers; if false, to // the treasury // Modifiers // -------------------------------------------------------------------------------------------------------- // Require that the collateral exists in the controller. If it is not the 0th index, and the // index is still 0 then it does not exist in the mapping. // no require here for valid collateral 0 index because that means it exists. modifier exists(address _collateral) { _exists(_collateral); _; } modifier onlyTimelock() { if (isSetupInitialized) { if (msg.sender != timelockAddress) { revert AdminContract__OnlyTimelock(); } } else { if (msg.sender != owner()) { revert AdminContract__OnlyOwner(); } } _; } modifier safeCheck( string memory parameter, address _collateral, uint256 enteredValue, uint256 min, uint256 max ) { if (!collateralParams[_collateral].active) { revert AdminContract__CollateralNotConfigured(); } if (enteredValue < min || enteredValue > max) { revert SafeCheckError(parameter, enteredValue, min, max); } _; } // Initializers // ----------------------------------------------------------------------------------------------------- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } /** * @dev The deployment script will call this function when all initial collaterals have been * configured; * after this is set to true, all subsequent config/setters will need to go through the * timelocks. */ function setSetupIsInitialized() external onlyTimelock { isSetupInitialized = true; } // External Functions // ----------------------------------------------------------------------------------------------- function addNewCollateral( address _collateral, uint256 _debtTokenGasCompensation // the gas compensation is initialized here as it won't // be changed ) external override onlyTimelock { if (collateralParams[_collateral].mcr != 0) { revert AdminContract__CollateralExists(); } // require(_decimals == DEFAULT_DECIMALS, "collaterals must have the default decimals"); validCollateral.push(_collateral); collateralParams[_collateral] = CollateralParams({ index: validCollateral.length - 1, active: false, borrowingFee: BORROWING_FEE_DEFAULT, ccr: CCR_DEFAULT, mcr: MCR_DEFAULT, debtTokenGasCompensation: _debtTokenGasCompensation, minNetDebt: MIN_NET_DEBT_DEFAULT, mintCap: MINT_CAP_DEFAULT, percentDivisor: PERCENT_DIVISOR_DEFAULT, redemptionFeeFloor: REDEMPTION_FEE_FLOOR_DEFAULT, redemptionBlockTimestamp: REDEMPTION_BLOCK_TIMESTAMP_DEFAULT }); emit CollateralAdded(_collateral); IStabilityPool(stabilityPool).addCollateralType(_collateral); } function setCollateralParameters( address _collateral, uint256 borrowingFee, uint256 ccr, uint256 mcr, uint256 minNetDebt, uint256 mintCap, uint256 percentDivisor, uint256 redemptionFeeFloor ) public override onlyTimelock { collateralParams[_collateral].active = true; setBorrowingFee(_collateral, borrowingFee); setCCR(_collateral, ccr); setMCR(_collateral, mcr); setMinNetDebt(_collateral, minNetDebt); setMintCap(_collateral, mintCap); setPercentDivisor(_collateral, percentDivisor); setRedemptionFeeFloor(_collateral, redemptionFeeFloor); } function setIsActive(address _collateral, bool _active) external onlyTimelock { CollateralParams storage collParams = collateralParams[_collateral]; collParams.active = _active; } function setBorrowingFee( address _collateral, uint256 borrowingFee ) public override onlyTimelock safeCheck("Borrowing Fee", _collateral, borrowingFee, 0, 0.1 ether) // 0% - 10% { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldBorrowing = collParams.borrowingFee; collParams.borrowingFee = borrowingFee; emit BorrowingFeeChanged(oldBorrowing, borrowingFee); } function setCCR( address _collateral, uint256 newCCR ) public override onlyTimelock safeCheck("CCR", _collateral, newCCR, 1 ether, 10 ether) // 100% - 1,000% { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldCCR = collParams.ccr; collParams.ccr = newCCR; emit CCRChanged(oldCCR, newCCR); } function setMCR( address _collateral, uint256 newMCR ) public override onlyTimelock safeCheck("MCR", _collateral, newMCR, 1.01 ether, 10 ether) // 101% - 1,000% { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldMCR = collParams.mcr; collParams.mcr = newMCR; emit MCRChanged(oldMCR, newMCR); } function setMinNetDebt( address _collateral, uint256 minNetDebt ) public override onlyTimelock safeCheck("Min Net Debt", _collateral, minNetDebt, 0, 2000 ether) { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldMinNet = collParams.minNetDebt; collParams.minNetDebt = minNetDebt; emit MinNetDebtChanged(oldMinNet, minNetDebt); } function setMintCap(address _collateral, uint256 mintCap) public override onlyTimelock { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldMintCap = collParams.mintCap; collParams.mintCap = mintCap; emit MintCapChanged(oldMintCap, mintCap); } function setPercentDivisor( address _collateral, uint256 percentDivisor ) public override onlyTimelock safeCheck("Percent Divisor", _collateral, percentDivisor, 2, 200) { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldPercent = collParams.percentDivisor; collParams.percentDivisor = percentDivisor; emit PercentDivisorChanged(oldPercent, percentDivisor); } function setRedemptionFeeFloor( address _collateral, uint256 redemptionFeeFloor ) public override onlyTimelock safeCheck("Redemption Fee Floor", _collateral, redemptionFeeFloor, 0.001 ether, 0.1 ether) // 0.10% // - 10% { CollateralParams storage collParams = collateralParams[_collateral]; uint256 oldRedemptionFeeFloor = collParams.redemptionFeeFloor; collParams.redemptionFeeFloor = redemptionFeeFloor; emit RedemptionFeeFloorChanged(oldRedemptionFeeFloor, redemptionFeeFloor); } function setRedemptionBlockTimestamp( address _collateral, uint256 _blockTimestamp ) public override onlyTimelock { collateralParams[_collateral].redemptionBlockTimestamp = _blockTimestamp; emit RedemptionBlockTimestampChanged(_collateral, _blockTimestamp); } function setFeeForFlashLoan(uint256 _flashLoanFee) external onlyTimelock { uint256 oldFlashLoanFee = flashLoanParams.flashLoanFee; flashLoanParams.flashLoanFee = _flashLoanFee; emit FlashLoanFeeChanged(oldFlashLoanFee, _flashLoanFee); } function setMinDebtForFlashLoan(uint256 _flashLoanMinDebt) external onlyTimelock { uint256 oldFlashLoanMinDebt = flashLoanParams.flashLoanMinDebt; flashLoanParams.flashLoanMinDebt = _flashLoanMinDebt; emit FlashLoanMinDebtChanged(oldFlashLoanMinDebt, _flashLoanMinDebt); } function setMaxDebtForFlashLoan(uint256 _flashLoanMaxDebt) external onlyTimelock { uint256 oldFlashLoanMaxDebt = flashLoanParams.flashLoanMaxDebt; flashLoanParams.flashLoanMaxDebt = _flashLoanMaxDebt; emit FlashLoanMaxDebtChanged(oldFlashLoanMaxDebt, _flashLoanMaxDebt); } function switchRouteToTRENStaking() external onlyTimelock { if (routeToTRENStaking) { routeToTRENStaking = false; } else { routeToTRENStaking = true; } } // View functions // --------------------------------------------------------------------------------------------------- function DECIMAL_PRECISION() external pure returns (uint256) { return _DECIMAL_PRECISION; } function getValidCollateral() external view override returns (address[] memory) { return validCollateral; } function getIsActive(address _collateral) external view override exists(_collateral) returns (bool) { return collateralParams[_collateral].active; } function getIndex(address _collateral) external view override exists(_collateral) returns (uint256) { return (collateralParams[_collateral].index); } function getIndices(address[] memory _colls) external view returns (uint256[] memory indices) { uint256 len = _colls.length; indices = new uint256[](len); for (uint256 i; i < len;) { _exists(_colls[i]); indices[i] = collateralParams[_colls[i]].index; unchecked { i++; } } } function getMcr(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].mcr; } function getCcr(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].ccr; } function getDebtTokenGasCompensation(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].debtTokenGasCompensation; } function getMinNetDebt(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].minNetDebt; } function getPercentDivisor(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].percentDivisor; } function getBorrowingFee(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].borrowingFee; } function getRedemptionFeeFloor(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].redemptionFeeFloor; } function getRedemptionBlockTimestamp(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].redemptionBlockTimestamp; } function getMintCap(address _collateral) external view override returns (uint256) { return collateralParams[_collateral].mintCap; } function getTotalAssetDebt(address _asset) external view override returns (uint256) { return IActivePool(activePool).getDebtTokenBalance(_asset) + IDefaultPool(defaultPool).getDebtTokenBalance(_asset); } function getFlashLoanFee() external view override returns (uint256) { return flashLoanParams.flashLoanFee; } function getFlashLoanMinNetDebt() external view override returns (uint256) { return flashLoanParams.flashLoanMinDebt; } function getFlashLoanMaxNetDebt() external view override returns (uint256) { return flashLoanParams.flashLoanMaxDebt; } function getRouteToTRENStaking() external view override returns (bool) { return routeToTRENStaking; } // Internal Functions // ----------------------------------------------------------------------------------------------- function _exists(address _collateral) internal view { if (collateralParams[_collateral].mcr == 0) { revert AdminContract__CollateralDoesNotExist(); } } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { TrenMath } from "./Dependencies/TrenMath.sol"; import { TrenBase } from "./Dependencies/TrenBase.sol"; import { SafetyTransfer } from "./Dependencies/SafetyTransfer.sol"; import { IDefaultPool } from "./Interfaces/IDefaultPool.sol"; import { IPriceFeed } from "./Interfaces/IPriceFeed.sol"; import { ISortedTrenBoxes } from "./Interfaces/ISortedTrenBoxes.sol"; import { IActivePool } from "./Interfaces/IActivePool.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; import { ITrenBoxManager } from "./Interfaces/ITrenBoxManager.sol"; import { IBorrowerOperations } from "./Interfaces/IBorrowerOperations.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; import { IFeeCollector } from "./Interfaces/IFeeCollector.sol"; import { ICollSurplusPool } from "./Interfaces/ICollSurplusPool.sol"; import { IDeposit } from "./Interfaces/IDeposit.sol"; contract BorrowerOperations is TrenBase, ReentrancyGuardUpgradeable, UUPSUpgradeable, IBorrowerOperations { using SafeERC20 for IERC20; string public constant NAME = "BorrowerOperations"; // --- Initializer --- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } // --- Borrower TrenBox Operations --- function openTrenBox( address _asset, uint256 _assetAmount, uint256 _debtTokenAmount, address _upperHint, address _lowerHint ) external override nonReentrant { if (!IAdminContract(adminContract).getIsActive(_asset)) { revert BorrowerOperations__NotActiveColl(); } OpenTrenBox memory vars; vars.asset = _asset; vars.price = IPriceFeed(priceFeed).fetchPrice(vars.asset); bool isRecoveryMode = _checkRecoveryMode(vars.asset, vars.price); uint256 status = ITrenBoxManager(trenBoxManager).getTrenBoxStatus(vars.asset, msg.sender); // require TrenBox is not active if (status == 1) { revert BorrowerOperations__TrenBoxIsActive(); } vars.netDebt = _debtTokenAmount; if (!isRecoveryMode) { vars.debtTokenFee = _triggerBorrowingFee(vars.asset, _debtTokenAmount); vars.netDebt = vars.netDebt + vars.debtTokenFee; } _requireAtLeastMinNetDebt(vars.asset, vars.netDebt); // ICR is based on the composite debt, i.e. the requested debt token amount + borrowing fee // + gas comp. uint256 gasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(vars.asset); vars.compositeDebt = vars.netDebt + gasCompensation; if (vars.compositeDebt == 0) { revert BorrowerOperations__CompositeDebtZero(); } vars.ICR = TrenMath._computeCR(_assetAmount, vars.compositeDebt, vars.price); vars.NICR = TrenMath._computeNominalCR(_assetAmount, vars.compositeDebt); if (isRecoveryMode) { _requireICRisAboveCCR(vars.asset, vars.ICR); } else { _requireICRisAboveMCR(vars.asset, vars.ICR); uint256 newTCR = _getNewTCRFromTrenBoxChange( vars.asset, _assetAmount, true, vars.compositeDebt, true, vars.price ); // bools: coll increase, debt increase _requireNewTCRisAboveCCR(vars.asset, newTCR); } // Set the trenBox struct's properties ITrenBoxManager(trenBoxManager).setTrenBoxStatus(vars.asset, msg.sender, 1); uint256 collateralAmountAfterIncrease = ITrenBoxManager(trenBoxManager).increaseTrenBoxColl( vars.asset, msg.sender, _assetAmount ); uint256 debtAmount_ = ITrenBoxManager(trenBoxManager).increaseTrenBoxDebt( vars.asset, msg.sender, vars.compositeDebt ); ITrenBoxManager(trenBoxManager).updateTrenBoxRewardSnapshots(vars.asset, msg.sender); vars.stake = ITrenBoxManager(trenBoxManager).updateStakeAndTotalStakes(vars.asset, msg.sender); ISortedTrenBoxes(sortedTrenBoxes).insert( vars.asset, msg.sender, vars.NICR, _upperHint, _lowerHint ); vars.arrayIndex = ITrenBoxManager(trenBoxManager).addTrenBoxOwnerToArray(vars.asset, msg.sender); emit TrenBoxCreated(vars.asset, msg.sender, vars.arrayIndex); // Move the asset to the Active Pool, and mint the debtToken amount to the borrower _activePoolAddColl(vars.asset, _assetAmount); _withdrawDebtTokens(vars.asset, msg.sender, _debtTokenAmount, vars.netDebt); // Move the debtToken gas compensation to the Gas Pool if (gasCompensation != 0) { _withdrawDebtTokens(vars.asset, gasPoolAddress, gasCompensation, gasCompensation); } emit TrenBoxUpdated( vars.asset, msg.sender, debtAmount_, collateralAmountAfterIncrease, vars.stake, BorrowerOperation.openTrenBox ); emit BorrowingFeePaid(vars.asset, msg.sender, vars.debtTokenFee); } // Send collateral to a trenBox function addColl( address _asset, uint256 _assetSent, address _upperHint, address _lowerHint ) external override nonReentrant { _adjustTrenBox(_asset, _assetSent, msg.sender, 0, 0, false, _upperHint, _lowerHint); } // Withdraw collateral from a trenBox function withdrawColl( address _asset, uint256 _collWithdrawal, address _upperHint, address _lowerHint ) external override nonReentrant { _adjustTrenBox(_asset, 0, msg.sender, _collWithdrawal, 0, false, _upperHint, _lowerHint); } // Withdraw debt tokens from a trenBox: mint new debt tokens to the owner, and increase the // trenBox's debt accordingly function withdrawDebtTokens( address _asset, uint256 _debtTokenAmount, address _upperHint, address _lowerHint ) external override nonReentrant { _adjustTrenBox(_asset, 0, msg.sender, 0, _debtTokenAmount, true, _upperHint, _lowerHint); } // Repay debt tokens to a TrenBox: Burn the repaid debt tokens, and reduce the trenBox's debt // accordingly function repayDebtTokens( address _asset, uint256 _debtTokenAmount, address _upperHint, address _lowerHint ) external override nonReentrant { _adjustTrenBox(_asset, 0, msg.sender, 0, _debtTokenAmount, false, _upperHint, _lowerHint); } function adjustTrenBox( address _asset, uint256 _assetSent, uint256 _collWithdrawal, uint256 _debtTokenChange, bool _isDebtIncrease, address _upperHint, address _lowerHint ) external override nonReentrant { _adjustTrenBox( _asset, _assetSent, msg.sender, _collWithdrawal, _debtTokenChange, _isDebtIncrease, _upperHint, _lowerHint ); } /** * @dev _adjustTrenBox(): Alongside a debt change, this function can perform either a collateral * top-up or a collateral withdrawal. */ function _adjustTrenBox( address _asset, uint256 _assetSent, address _borrower, uint256 _collWithdrawal, uint256 _debtTokenChange, bool _isDebtIncrease, address _upperHint, address _lowerHint ) internal { AdjustTrenBox memory vars; vars.asset = _asset; vars.price = IPriceFeed(priceFeed).fetchPrice(vars.asset); bool isRecoveryMode = _checkRecoveryMode(vars.asset, vars.price); if (_isDebtIncrease) { if (_debtTokenChange == 0) { revert BorrowerOperations__ZeroDebtChange(); } } _requireSingularCollChange(_collWithdrawal, _assetSent); _requireNonZeroAdjustment(_collWithdrawal, _debtTokenChange, _assetSent); _requireTrenBoxIsActive(vars.asset, _borrower); // Confirm the operation is either a borrower adjusting their own trenBox, or a pure asset // transfer from the Stability Pool to a trenBox assert( msg.sender == _borrower || (stabilityPool == msg.sender && _assetSent != 0 && _debtTokenChange == 0) ); ITrenBoxManager(trenBoxManager).applyPendingRewards(vars.asset, _borrower); // Get the collChange based on whether or not asset was sent in the transaction (vars.collChange, vars.isCollIncrease) = _getCollChange(_assetSent, _collWithdrawal); vars.netDebtChange = _debtTokenChange; // If the adjustment incorporates a debt increase and system is in Normal Mode, then trigger // a borrowing fee if (_isDebtIncrease && !isRecoveryMode) { vars.debtTokenFee = _triggerBorrowingFee(vars.asset, _debtTokenChange); vars.netDebtChange = vars.netDebtChange + vars.debtTokenFee; // The raw debt change // includes the fee } vars.debt = ITrenBoxManager(trenBoxManager).getTrenBoxDebt(vars.asset, _borrower); vars.coll = ITrenBoxManager(trenBoxManager).getTrenBoxColl(vars.asset, _borrower); // Get the trenBox's old ICR before the adjustment, and what its new ICR will be after the // adjustment vars.oldICR = TrenMath._computeCR(vars.coll, vars.debt, vars.price); vars.newICR = _getNewICRFromTrenBoxChange( vars.coll, vars.debt, vars.collChange, vars.isCollIncrease, vars.netDebtChange, _isDebtIncrease, vars.price ); if (_collWithdrawal > vars.coll) { revert BorrowerOperations__InsufficientCollateral(); } // Check the adjustment satisfies all conditions for the current system mode _requireValidAdjustmentInCurrentMode( vars.asset, isRecoveryMode, _collWithdrawal, _isDebtIncrease, vars ); // When the adjustment is a debt repayment, check it's a valid amount and that the caller // has enough debt tokens if (!_isDebtIncrease && _debtTokenChange != 0) { _requireAtLeastMinNetDebt( vars.asset, _getNetDebt(vars.asset, vars.debt) - vars.netDebtChange ); _requireValidDebtTokenRepayment(vars.asset, vars.debt, vars.netDebtChange); _requireSufficientDebtTokenBalance(_borrower, vars.netDebtChange); } (vars.newColl, vars.newDebt) = _updateTrenBoxFromAdjustment( vars.asset, _borrower, vars.collChange, vars.isCollIncrease, vars.netDebtChange, _isDebtIncrease ); vars.stake = ITrenBoxManager(trenBoxManager).updateStakeAndTotalStakes(vars.asset, _borrower); // Re-insert trenBox in to the sorted list uint256 newNICR = _getNewNominalICRFromTrenBoxChange( vars.coll, vars.debt, vars.collChange, vars.isCollIncrease, vars.netDebtChange, _isDebtIncrease ); ISortedTrenBoxes(sortedTrenBoxes).reInsert( vars.asset, _borrower, newNICR, _upperHint, _lowerHint ); emit TrenBoxUpdated( vars.asset, _borrower, vars.newDebt, vars.newColl, vars.stake, BorrowerOperation.adjustTrenBox ); emit BorrowingFeePaid(vars.asset, msg.sender, vars.debtTokenFee); // Use the unmodified _debtTokenChange here, as we don't send the fee to the user _moveTokensFromAdjustment( vars.asset, msg.sender, vars.collChange, vars.isCollIncrease, _debtTokenChange, _isDebtIncrease, vars.netDebtChange ); } function closeTrenBox(address _asset) external override nonReentrant { _requireTrenBoxIsActive(_asset, msg.sender); uint256 price = IPriceFeed(priceFeed).fetchPrice(_asset); _requireNotInRecoveryMode(_asset, price); ITrenBoxManager(trenBoxManager).applyPendingRewards(_asset, msg.sender); uint256 coll = ITrenBoxManager(trenBoxManager).getTrenBoxColl(_asset, msg.sender); uint256 debt = ITrenBoxManager(trenBoxManager).getTrenBoxDebt(_asset, msg.sender); uint256 gasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); uint256 refund = IFeeCollector(feeCollector).simulateRefund(msg.sender, _asset, 1 ether); uint256 netDebt = debt - gasCompensation - refund; _requireSufficientDebtTokenBalance(msg.sender, netDebt); uint256 newTCR = _getNewTCRFromTrenBoxChange(_asset, coll, false, debt, false, price); _requireNewTCRisAboveCCR(_asset, newTCR); ITrenBoxManager(trenBoxManager).removeStake(_asset, msg.sender); ITrenBoxManager(trenBoxManager).closeTrenBox(_asset, msg.sender); emit TrenBoxUpdated(_asset, msg.sender, 0, 0, 0, BorrowerOperation.closeTrenBox); // Burn the repaid debt tokens from the user's balance and the gas compensation from the Gas // Pool _repayDebtTokens(_asset, msg.sender, netDebt, refund); if (gasCompensation != 0) { _repayDebtTokens(_asset, gasPoolAddress, gasCompensation, 0); } // Signal to the fee collector that debt has been paid in full IFeeCollector(feeCollector).closeDebt(msg.sender, _asset); // Send the collateral back to the user IActivePool(activePool).sendAsset(_asset, msg.sender, coll); } /** * Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery * Mode */ function claimCollateral(address _asset) external override { // send asset from CollSurplusPool to owner ICollSurplusPool(collSurplusPool).claimColl(_asset, msg.sender); } function _triggerBorrowingFee( address _asset, uint256 _debtTokenAmount ) internal returns (uint256) { uint256 debtTokenFee = ITrenBoxManager(trenBoxManager).getBorrowingFee(_asset, _debtTokenAmount); IDebtToken(debtToken).mint(_asset, feeCollector, debtTokenFee); IFeeCollector(feeCollector).increaseDebt(msg.sender, _asset, debtTokenFee); return debtTokenFee; } function _getCollChange( uint256 _collReceived, uint256 _requestedCollWithdrawal ) internal pure returns (uint256 collChange, bool isCollIncrease) { if (_collReceived != 0) { collChange = _collReceived; isCollIncrease = true; } else { collChange = _requestedCollWithdrawal; } } // Update trenBox's coll and debt based on whether they increase or decrease function _updateTrenBoxFromAdjustment( address _asset, address _borrower, uint256 _collChange, bool _isCollIncrease, uint256 _debtChange, bool _isDebtIncrease ) internal returns (uint256, uint256) { uint256 newColl = (_isCollIncrease) ? ITrenBoxManager(trenBoxManager).increaseTrenBoxColl(_asset, _borrower, _collChange) : ITrenBoxManager(trenBoxManager).decreaseTrenBoxColl(_asset, _borrower, _collChange); uint256 newDebt = (_isDebtIncrease) ? ITrenBoxManager(trenBoxManager).increaseTrenBoxDebt(_asset, _borrower, _debtChange) : ITrenBoxManager(trenBoxManager).decreaseTrenBoxDebt(_asset, _borrower, _debtChange); return (newColl, newDebt); } function _moveTokensFromAdjustment( address _asset, address _borrower, uint256 _collChange, bool _isCollIncrease, uint256 _debtTokenChange, bool _isDebtIncrease, uint256 _netDebtChange ) internal { if (_isDebtIncrease) { _withdrawDebtTokens(_asset, _borrower, _debtTokenChange, _netDebtChange); } else { _repayDebtTokens(_asset, _borrower, _debtTokenChange, 0); } if (_isCollIncrease) { _activePoolAddColl(_asset, _collChange); } else { IActivePool(activePool).sendAsset(_asset, _borrower, _collChange); } } // Send asset to Active Pool and increase its recorded asset balance function _activePoolAddColl(address _asset, uint256 _amount) internal { IDeposit(activePool).receivedERC20(_asset, _amount); IERC20(_asset).safeTransferFrom( msg.sender, activePool, SafetyTransfer.decimalsCorrection(_asset, _amount) ); } // Issue the specified amount of debt tokens to _account and increases the total active debt // (_netDebtIncrease potentially includes a debtTokenFee) function _withdrawDebtTokens( address _asset, address _account, uint256 _debtTokenAmount, uint256 _netDebtIncrease ) internal { uint256 newTotalAssetDebt = IActivePool(activePool).getDebtTokenBalance(_asset) + IDefaultPool(defaultPool).getDebtTokenBalance(_asset) + _netDebtIncrease; if (newTotalAssetDebt > IAdminContract(adminContract).getMintCap(_asset)) { revert BorrowerOperations__ExceedMintCap(); } IActivePool(activePool).increaseDebt(_asset, _netDebtIncrease); IDebtToken(debtToken).mint(_asset, _account, _debtTokenAmount); } // Burn the specified amount of debt tokens from _account and decreases the total active debt function _repayDebtTokens( address _asset, address _account, uint256 _debtTokenAmount, uint256 _refund ) internal { /// @dev the borrowing fee partial refund is accounted for when decreasing the debt, as it /// was included when trenBox was opened IActivePool(activePool).decreaseDebt(_asset, _debtTokenAmount + _refund); /// @dev the borrowing fee partial refund is not burned here, as it has already been burned /// by the FeeCollector IDebtToken(debtToken).burn(_account, _debtTokenAmount); } // --- 'Require' wrapper functions --- function _requireSingularCollChange( uint256 _collWithdrawal, uint256 _amountSent ) internal pure { if (_collWithdrawal != 0 && _amountSent != 0) { revert BorrowerOperations__NotSingularChange(); } } function _requireNonZeroAdjustment( uint256 _collWithdrawal, uint256 _debtTokenChange, uint256 _assetSent ) internal pure { if (_collWithdrawal == 0 && _debtTokenChange == 0 && _assetSent == 0) { revert BorrowerOperations__ZeroAdjustment(); } } function _requireTrenBoxIsActive(address _asset, address _borrower) internal view { uint256 status = ITrenBoxManager(trenBoxManager).getTrenBoxStatus(_asset, _borrower); if (status != 1) { revert BorrowerOperations__TrenBoxNotExistOrClosed(); } } function _requireNotInRecoveryMode(address _asset, uint256 _price) internal view { if (_checkRecoveryMode(_asset, _price)) { revert BorrowerOperations__OperationInRecoveryMode(); } } function _requireNoCollWithdrawal(uint256 _collWithdrawal) internal pure { if (_collWithdrawal != 0) { revert BorrowerOperations__CollWithdrawalInRecoveryMode(); } } function _requireValidAdjustmentInCurrentMode( address _asset, bool _isRecoveryMode, uint256 _collWithdrawal, bool _isDebtIncrease, AdjustTrenBox memory _vars ) internal view { /* * In Recovery Mode, only allow: * * - Pure collateral top-up * - Pure debt repayment * - Collateral top-up with debt repayment * - A debt increase combined with a collateral top-up which makes the ICR >= 150% and improves the ICR (and by extension improves the TCR). * * In Normal Mode, ensure: * * - The new ICR is above MCR * - The adjustment won't pull the TCR below CCR */ if (_isRecoveryMode) { _requireNoCollWithdrawal(_collWithdrawal); if (_isDebtIncrease) { _requireICRisAboveCCR(_asset, _vars.newICR); _requireNewICRisAboveOldICR(_vars.newICR, _vars.oldICR); } } else { // if Normal Mode _requireICRisAboveMCR(_asset, _vars.newICR); _vars.newTCR = _getNewTCRFromTrenBoxChange( _asset, _vars.collChange, _vars.isCollIncrease, _vars.netDebtChange, _isDebtIncrease, _vars.price ); _requireNewTCRisAboveCCR(_asset, _vars.newTCR); } } function _requireICRisAboveMCR(address _asset, uint256 _newICR) internal view { if (_newICR < IAdminContract(adminContract).getMcr(_asset)) { revert BorrowerOperations__TrenBoxICRBelowMCR(); } } function _requireICRisAboveCCR(address _asset, uint256 _newICR) internal view { if (_newICR < IAdminContract(adminContract).getCcr(_asset)) { revert BorrowerOperations__TrenBoxICRBelowCCR(); } } function _requireNewICRisAboveOldICR(uint256 _newICR, uint256 _oldICR) internal pure { if (_newICR < _oldICR) { revert BorrowerOperations__TrenBoxNewICRBelowOldICR(); } } function _requireNewTCRisAboveCCR(address _asset, uint256 _newTCR) internal view { if (_newTCR < IAdminContract(adminContract).getCcr(_asset)) { revert BorrowerOperations__TrenBoxNewTCRBelowCCR(); } } function _requireAtLeastMinNetDebt(address _asset, uint256 _netDebt) internal view { if (_netDebt < IAdminContract(adminContract).getMinNetDebt(_asset)) { revert BorrowerOperations__TrenBoxNetDebtLessThanMin(); } } function _requireValidDebtTokenRepayment( address _asset, uint256 _currentDebt, uint256 _debtRepayment ) internal view { if ( _debtRepayment > _currentDebt - IAdminContract(adminContract).getDebtTokenGasCompensation(_asset) ) { revert BorrowerOperations__RepayLargerThanTrenBoxDebt(); } } function _requireSufficientDebtTokenBalance( address _borrower, uint256 _debtRepayment ) internal view { if (IDebtToken(debtToken).balanceOf(_borrower) < _debtRepayment) { revert BorrowerOperations__InsufficientDebtBalance(); } } // --- ICR and TCR getters --- // Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending // rewards. function _getNewNominalICRFromTrenBoxChange( uint256 _coll, uint256 _debt, uint256 _collChange, bool _isCollIncrease, uint256 _debtChange, bool _isDebtIncrease ) internal pure returns (uint256) { (uint256 newColl, uint256 newDebt) = _getNewTrenBoxAmounts( _coll, _debt, _collChange, _isCollIncrease, _debtChange, _isDebtIncrease ); uint256 newNICR = TrenMath._computeNominalCR(newColl, newDebt); return newNICR; } // Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending // rewards. function _getNewICRFromTrenBoxChange( uint256 _coll, uint256 _debt, uint256 _collChange, bool _isCollIncrease, uint256 _debtChange, bool _isDebtIncrease, uint256 _price ) internal pure returns (uint256) { (uint256 newColl, uint256 newDebt) = _getNewTrenBoxAmounts( _coll, _debt, _collChange, _isCollIncrease, _debtChange, _isDebtIncrease ); uint256 newICR = TrenMath._computeCR(newColl, newDebt, _price); return newICR; } function _getNewTrenBoxAmounts( uint256 _coll, uint256 _debt, uint256 _collChange, bool _isCollIncrease, uint256 _debtChange, bool _isDebtIncrease ) internal pure returns (uint256, uint256) { uint256 newColl = _coll; uint256 newDebt = _debt; newColl = _isCollIncrease ? _coll + _collChange : _coll - _collChange; newDebt = _isDebtIncrease ? _debt + _debtChange : _debt - _debtChange; return (newColl, newDebt); } function _getNewTCRFromTrenBoxChange( address _asset, uint256 _collChange, bool _isCollIncrease, uint256 _debtChange, bool _isDebtIncrease, uint256 _price ) internal view returns (uint256) { uint256 totalColl = getEntireSystemColl(_asset); uint256 totalDebt = getEntireSystemDebt(_asset); totalColl = _isCollIncrease ? totalColl + _collChange : totalColl - _collChange; totalDebt = _isDebtIncrease ? totalDebt + _debtChange : totalDebt - _debtChange; uint256 newTCR = TrenMath._computeCR(totalColl, totalDebt, _price); return newTCR; } function getCompositeDebt( address _asset, uint256 _debt ) external view override returns (uint256) { return _getCompositeDebt(_asset, _debt); } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ICollSurplusPool } from "./Interfaces/ICollSurplusPool.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { SafetyTransfer } from "./Dependencies/SafetyTransfer.sol"; contract CollSurplusPool is UUPSUpgradeable, OwnableUpgradeable, ICollSurplusPool, ConfigurableAddresses { using SafeERC20 for IERC20; string public constant NAME = "CollSurplusPool"; mapping(address collateral => uint256 balance) internal collateralBalances; mapping(address user => mapping(address collateral => uint256 balance)) internal userBalances; // --- modifiers --- modifier onlyBorrowerOperations() { if (msg.sender != borrowerOperations) { revert CollSurplusPool__NotBorrowerOperations(); } _; } modifier onlyTrenBoxManager() { if (msg.sender != trenBoxManager) { revert CollSurplusPool__NotTrenBoxManager(); } _; } modifier onlyActivePool() { if (msg.sender != activePool) { revert CollSurplusPool__NotActivePool(); } _; } // --- Initializer --- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } function getAssetBalance(address _asset) external view override returns (uint256) { return collateralBalances[_asset]; } function getCollateral( address _asset, address _account ) external view override returns (uint256) { return userBalances[_account][_asset]; } // --- Pool functionality --- function accountSurplus( address _asset, address _account, uint256 _amount ) external override onlyTrenBoxManager { mapping(address => uint256) storage userBalance = userBalances[_account]; uint256 newAmount = userBalance[_asset] + _amount; userBalance[_asset] = newAmount; emit CollBalanceUpdated(_account, newAmount); } function claimColl(address _asset, address _account) external override onlyBorrowerOperations { mapping(address => uint256) storage userBalance = userBalances[_account]; uint256 claimableColl = userBalance[_asset]; uint256 safetyTransferclaimableColl = SafetyTransfer.decimalsCorrection(_asset, claimableColl); if (safetyTransferclaimableColl == 0) { revert CollSurplusPool__NoClaimableColl(); } userBalance[_asset] = 0; emit CollBalanceUpdated(_account, 0); collateralBalances[_asset] = collateralBalances[_asset] - claimableColl; emit AssetSent(_account, safetyTransferclaimableColl); IERC20(_asset).safeTransfer(_account, safetyTransferclaimableColl); } function receivedERC20(address _asset, uint256 _amount) external override onlyActivePool { collateralBalances[_asset] = collateralBalances[_asset] + _amount; } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; contract DebtToken is IDebtToken, ERC20Permit, Ownable { string public constant NAME = "TREN Debt Token"; string public constant SYMBOL = "trenUSD"; address public borrowerOperationsAddress; address public stabilityPoolAddress; address public trenBoxManagerAddress; mapping(address collateral => bool isStopped) public emergencyStopMintingCollateral; mapping(address whitelistedContract => bool isWhitelisted) public whitelistedContracts; modifier onlyWhitelistedContract() { if (!whitelistedContracts[msg.sender]) { revert DebtToken__NotWhitelistedContract(msg.sender); } _; } modifier shouldTransferToValidRecipent(address _recipient) { if (_recipient == address(0)) { revert DebtToken__CannotTransferTokensToZeroAddress(); } else if (_recipient == address(this)) { revert DebtToken__CannotTransferTokensToTokenContract(); } _; } modifier onlyBorrowerOperations() { if (msg.sender != borrowerOperationsAddress) { revert DebtToken__CallerIsNotBorrowerOperations(msg.sender); } _; } modifier onlyStabilityPool() { if (msg.sender != stabilityPoolAddress) { revert DebtToken__CallerIsNotStabilityPool(msg.sender); } _; } constructor(address initialOwner) ERC20(NAME, SYMBOL) ERC20Permit(NAME) Ownable(initialOwner) { } function emergencyStopMinting(address _asset, bool status) external override onlyOwner { emergencyStopMintingCollateral[_asset] = status; emit EmergencyStopMintingCollateral(_asset, status); } function setAddresses( address _borrowerOperationsAddress, address _stabilityPoolAddress, address _trenBoxManagerAddress ) external onlyOwner { if ( _borrowerOperationsAddress == address(0) || _stabilityPoolAddress == address(0) || _trenBoxManagerAddress == address(0) ) { revert DebtToken__InvalidAddressToConnect(); } borrowerOperationsAddress = _borrowerOperationsAddress; stabilityPoolAddress = _stabilityPoolAddress; trenBoxManagerAddress = _trenBoxManagerAddress; emit ProtocolContractsAddressesSet( _borrowerOperationsAddress, _stabilityPoolAddress, _trenBoxManagerAddress ); } function mintFromWhitelistedContract(uint256 _amount) external override onlyWhitelistedContract { _mint(msg.sender, _amount); } function burnFromWhitelistedContract(uint256 _amount) external override onlyWhitelistedContract { _burn(msg.sender, _amount); } function mint( address _asset, address _account, uint256 _amount ) external override onlyBorrowerOperations { if (emergencyStopMintingCollateral[_asset]) { revert DebtToken__MintBlockedForCollateral(_asset); } _mint(_account, _amount); } function burn(address _account, uint256 _amount) external override { if ( msg.sender != borrowerOperationsAddress && msg.sender != trenBoxManagerAddress && msg.sender != stabilityPoolAddress ) { revert DebtToken__CannotBurnTokens(); } _burn(_account, _amount); } function addWhitelist(address _address) external override onlyOwner { whitelistedContracts[_address] = true; emit WhitelistChanged(_address, true); } function removeWhitelist(address _address) external override onlyOwner { whitelistedContracts[_address] = false; emit WhitelistChanged(_address, false); } function sendToPool( address _sender, address _poolAddress, uint256 _amount ) external override onlyStabilityPool { _transfer(_sender, _poolAddress, _amount); } function returnFromPool( address _poolAddress, address _receiver, uint256 _amount ) external override { if (msg.sender != stabilityPoolAddress && msg.sender != trenBoxManagerAddress) { revert DebtToken__CannotReturnFromPool(); } _transfer(_poolAddress, _receiver, _amount); } function transfer( address recipient, uint256 amount ) public override(IERC20, ERC20) shouldTransferToValidRecipent(recipient) returns (bool) { return super.transfer(recipient, amount); } function transferFrom( address sender, address recipient, uint256 amount ) public override(IERC20, ERC20) shouldTransferToValidRecipent(recipient) returns (bool) { return super.transferFrom(sender, recipient, amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { SafetyTransfer } from "./Dependencies/SafetyTransfer.sol"; import { IDefaultPool } from "./Interfaces/IDefaultPool.sol"; import { IDeposit } from "./Interfaces/IDeposit.sol"; /** * @notice The Default Pool holds the collateral and debt token amounts from liquidations that have * been redistributed to active trenBoxes * but not yet "applied", i.e. not yet recorded on a recipient active trenBox's struct. * * When a trenBox makes an operation that applies to its pending collateral and debt, they are moved * from the Default Pool to the Active Pool. */ contract DefaultPool is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable, IDefaultPool, IDeposit, ConfigurableAddresses { using SafeERC20 for IERC20; string public constant NAME = "DefaultPool"; mapping(address collateral => uint256 collateralBalances) internal assetsBalances; mapping(address debt => uint256 debtBalances) internal debtTokenBalances; // --- modifiers --- modifier callerIsActivePool() { if (msg.sender != activePool) { revert DefaultPool__NotActivePool(); } _; } modifier callerIsTrenBoxManager() { if (msg.sender != trenBoxManager) { revert DefaultPool__NotTrenBoxManager(); } _; } // --- Initializer --- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } // --- Getters for public variables. Required by IPool interface --- function getAssetBalance(address _asset) external view override returns (uint256) { return assetsBalances[_asset]; } function getDebtTokenBalance(address _asset) external view override returns (uint256) { return debtTokenBalances[_asset]; } function increaseDebt( address _asset, uint256 _amount ) external override callerIsTrenBoxManager { uint256 newDebt = debtTokenBalances[_asset] + _amount; debtTokenBalances[_asset] = newDebt; emit DefaultPoolDebtUpdated(_asset, newDebt); } function decreaseDebt( address _asset, uint256 _amount ) external override callerIsTrenBoxManager { uint256 newDebt = debtTokenBalances[_asset] - _amount; debtTokenBalances[_asset] = newDebt; emit DefaultPoolDebtUpdated(_asset, newDebt); } // --- Pool functionality --- function sendAssetToActivePool( address _asset, uint256 _amount ) external override nonReentrant callerIsTrenBoxManager { uint256 safetyTransferAmount = SafetyTransfer.decimalsCorrection(_asset, _amount); if (safetyTransferAmount == 0) { return; } uint256 newBalance = assetsBalances[_asset] - _amount; assetsBalances[_asset] = newBalance; IERC20(_asset).safeTransfer(activePool, safetyTransferAmount); IDeposit(activePool).receivedERC20(_asset, _amount); emit DefaultPoolAssetBalanceUpdated(_asset, newBalance); emit AssetSent(activePool, _asset, safetyTransferAmount); } function receivedERC20(address _asset, uint256 _amount) external callerIsActivePool { uint256 newBalance = assetsBalances[_asset] + _amount; assetsBalances[_asset] = newBalance; emit DefaultPoolAssetBalanceUpdated(_asset, newBalance); } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; abstract contract ConfigurableAddresses is OwnableUpgradeable { address public activePool; address public adminContract; address public borrowerOperations; address public collSurplusPool; address public communityIssuance; address public debtToken; address public defaultPool; address public feeCollector; address public flashLoanAddress; address public gasPoolAddress; address public trenStaking; address public priceFeed; address public sortedTrenBoxes; address public stabilityPool; address public timelockAddress; address public treasuryAddress; address public trenBoxManager; address public trenBoxManagerOperations; bool public isAddressSetupInitialized; /** * @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[33] private __gap; // Goerli uses 47; Arbitrum uses 33 error ConfigurableAddresses__SetupIsInitialized(); error ConfigurableAddresses__ZeroAddresses(uint256 position, address address_); error ConfigurableAddresses__CommunityIssuanceZeroAddress(); error ConfigurableAddresses__TRENStakingZeroAddress(); error ConfigurableAddresses__LengthMismatch(); // Dependency setters // ----------------------------------------------------------------------------------------------- function setAddresses(address[] calldata _addresses) external onlyOwner { if (isAddressSetupInitialized) { revert ConfigurableAddresses__SetupIsInitialized(); } if (_addresses.length != 16) { revert ConfigurableAddresses__LengthMismatch(); } for (uint256 i = 0; i < 16; i++) { if (_addresses[i] == address(0)) { revert ConfigurableAddresses__ZeroAddresses(i, _addresses[i]); } } activePool = _addresses[0]; adminContract = _addresses[1]; borrowerOperations = _addresses[2]; collSurplusPool = _addresses[3]; debtToken = _addresses[4]; defaultPool = _addresses[5]; feeCollector = _addresses[6]; flashLoanAddress = _addresses[7]; gasPoolAddress = _addresses[8]; priceFeed = _addresses[9]; sortedTrenBoxes = _addresses[10]; stabilityPool = _addresses[11]; timelockAddress = _addresses[12]; treasuryAddress = _addresses[13]; trenBoxManager = _addresses[14]; trenBoxManagerOperations = _addresses[15]; isAddressSetupInitialized = true; } function setCommunityIssuance(address _communityIssuance) public onlyOwner { if (_communityIssuance == address(0)) { revert ConfigurableAddresses__CommunityIssuanceZeroAddress(); } communityIssuance = _communityIssuance; } function setTRENStaking(address _trenStaking) public onlyOwner { if (_trenStaking == address(0)) { revert ConfigurableAddresses__TRENStakingZeroAddress(); } trenStaking = _trenStaking; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IERC20Decimals } from "../Interfaces/IERC20Decimals.sol"; library SafetyTransfer { error EthUnsupportedError(); error InvalidAmountError(); //_amount is in ether (1e18) and we want to convert it to the token decimal function decimalsCorrection(address _token, uint256 _amount) internal view returns (uint256) { if (_token == address(0)) { revert EthUnsupportedError(); } if (_amount == 0) { return 0; } uint8 decimals = IERC20Decimals(_token).decimals(); if (decimals < 18) { uint256 divisor = 10 ** (18 - decimals); if (_amount % divisor != 0) { revert InvalidAmountError(); } return _amount / divisor; } else if (decimals > 18) { uint256 multiplier = 10 ** (decimals - 18); return _amount * multiplier; } return _amount; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ConfigurableAddresses } from "./ConfigurableAddresses.sol"; import { TrenMath } from "./TrenMath.sol"; import { IActivePool } from "../Interfaces/IActivePool.sol"; import { IDefaultPool } from "../Interfaces/IDefaultPool.sol"; import { IAdminContract } from "../Interfaces/IAdminContract.sol"; import { IDefaultPool } from "../Interfaces/IDefaultPool.sol"; import { TrenMath } from "./TrenMath.sol"; /* * Base contract for TrenBoxManager, BorrowerOperations and StabilityPool. Contains global system constants and * common functions. */ abstract contract TrenBase is OwnableUpgradeable, ConfigurableAddresses { struct Colls { address[] tokens; uint256[] amounts; } error TrenBase__FeeExceededMax(uint256 feePercentage, uint256 maxFeePercentage); // --- Gas compensation functions --- // Returns the composite debt (drawn debt + gas compensation) of a trenBox, for the purpose of // ICR calculation function _getCompositeDebt(address _asset, uint256 _debt) internal view returns (uint256) { return _debt + IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); } function _getNetDebt(address _asset, uint256 _debt) internal view returns (uint256) { return _debt - IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); } // Return the amount of ETH to be drawn from a trenBox's collateral and sent as gas // compensation. function _getCollGasCompensation( address _asset, uint256 _entireColl ) internal view returns (uint256) { return _entireColl / IAdminContract(adminContract).getPercentDivisor(_asset); } function getEntireSystemColl(address _asset) public view returns (uint256 entireSystemColl) { uint256 activeColl = IActivePool(activePool).getAssetBalance(_asset); uint256 liquidatedColl = IDefaultPool(defaultPool).getAssetBalance(_asset); return activeColl + liquidatedColl; } function getEntireSystemDebt(address _asset) public view returns (uint256 entireSystemDebt) { uint256 activeDebt = IActivePool(activePool).getDebtTokenBalance(_asset); uint256 closedDebt = IDefaultPool(defaultPool).getDebtTokenBalance(_asset); return activeDebt + closedDebt; } function _getTCR(address _asset, uint256 _price) internal view returns (uint256 TCR) { uint256 entireSystemColl = getEntireSystemColl(_asset); uint256 entireSystemDebt = getEntireSystemDebt(_asset); TCR = TrenMath._computeCR(entireSystemColl, entireSystemDebt, _price); } function _checkRecoveryMode(address _asset, uint256 _price) internal view returns (bool) { uint256 TCR = _getTCR(_asset, _price); return TCR < IAdminContract(adminContract).getCcr(_asset); } function _requireUserAcceptsFee( uint256 _fee, uint256 _amount, uint256 _maxFeePercentage ) internal view { uint256 feePercentage = (_fee * IAdminContract(adminContract).DECIMAL_PRECISION()) / _amount; if (feePercentage > _maxFeePercentage) { revert TrenBase__FeeExceededMax(feePercentage, _maxFeePercentage); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; uint256 constant DECIMAL_PRECISION = 1 ether; library TrenMath { uint256 internal constant EXPONENT_CAP = 525_600_000; /* Precision for Nominal ICR (independent of price). Rationale for the value: * * - Making it “too high” could lead to overflows. * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division. * * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH, * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator. * */ uint256 internal constant NICR_PRECISION = 1e20; function _min(uint256 _a, uint256 _b) internal pure returns (uint256) { return (_a < _b) ? _a : _b; } function _max(uint256 _a, uint256 _b) internal pure returns (uint256) { return (_a >= _b) ? _a : _b; } /* * Multiply two decimal numbers and use normal rounding rules: * -round product up if 19'th mantissa digit >= 5 * -round product down if 19'th mantissa digit < 5 * * Used only inside the exponentiation, _decPow(). */ function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) { uint256 prod_xy = x * y; decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION; } /* * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n. * * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity. * * Called by two functions that represent time in units of minutes: * 1) TrenBoxManager._calcDecayedBaseRate * 2) CommunityIssuance._getCumulativeIssuanceFraction * * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals * "minutes in 1000 years": 60 * 24 * 365 * 1000 * * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be * negligibly different from just passing the cap, since: * * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible */ function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint256) { if (_minutes > EXPONENT_CAP) { _minutes = EXPONENT_CAP; } // cap to avoid overflow if (_minutes == 0) { return DECIMAL_PRECISION; } uint256 y = DECIMAL_PRECISION; uint256 x = _base; uint256 n = _minutes; // Exponentiation-by-squaring while (n > 1) { if (n % 2 == 0) { x = decMul(x, x); n = n / 2; } else { // if (n % 2 != 0) y = decMul(x, y); x = decMul(x, x); n = (n - 1) / 2; } } return decMul(x, y); } function _getAbsoluteDifference(uint256 _a, uint256 _b) internal pure returns (uint256) { return (_a >= _b) ? _a - _b : _b - _a; } function _computeNominalCR(uint256 _coll, uint256 _debt) internal pure returns (uint256) { if (_debt != 0) { return (_coll * NICR_PRECISION) / _debt; } // Return the maximal value for uint256 if the TrenBox has a debt of 0. Represents // "infinite" // CR. else { // if (_debt == 0) return type(uint256).max; } } function _computeCR( uint256 _coll, uint256 _debt, uint256 _price ) internal pure returns (uint256) { if (_debt != 0) { uint256 newCollRatio = (_coll * _price) / _debt; return newCollRatio; } // Return the maximal value for uint256 if the TrenBox has a debt of 0. Represents // "infinite" // CR. else { // if (_debt == 0) return type(uint256).max; } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; import { IFeeCollector } from "./Interfaces/IFeeCollector.sol"; import { ITRENStaking } from "./Interfaces/ITRENStaking.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; contract FeeCollector is IFeeCollector, UUPSUpgradeable, OwnableUpgradeable, ConfigurableAddresses { using SafeERC20 for IERC20; // Constants // -------------------------------------------------------------------------------------------------------- string public constant NAME = "FeeCollector"; uint256 public constant MIN_FEE_DAYS = 7; uint256 public constant MIN_FEE_FRACTION = 0.038461538 * 1 ether; // (1/26) fee divided by 26 // weeks uint256 public constant FEE_EXPIRATION_SECONDS = 175 * 1 days; // ~ 6 months, minus one week // (MIN_FEE_DAYS) // State // ------------------------------------------------------------------------------------------------------------ mapping(address borrower => mapping(address asset => FeeRecord feeParams)) public feeRecords; // Initializer // ------------------------------------------------------------------------------------------------------ function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } // Public/external methods // ------------------------------------------------------------------------------------------ /** * Triggered when a trenBox is created and again whenever the borrower acquires additional * loans. * Collects the minimum fee to the platform, for which there is no refund; holds on to the * remaining fees until * debt is paid, liquidated, or expired. * * Attention: this method assumes that (debt token) _feeAmount has already been minted and * transferred to this contract. */ function increaseDebt( address _borrower, address _asset, uint256 _feeAmount ) external override onlyBorrowerOperations { uint256 minFeeAmount = (MIN_FEE_FRACTION * _feeAmount) / 1 ether; uint256 refundableFeeAmount = _feeAmount - minFeeAmount; uint256 feeToCollect = _createOrUpdateFeeRecord(_borrower, _asset, refundableFeeAmount); _collectFee(_borrower, _asset, minFeeAmount + feeToCollect); } /** * Triggered when a trenBox is adjusted or closed (and the borrower has paid back/decreased his * loan). */ function decreaseDebt( address _borrower, address _asset, uint256 _paybackFraction ) external override onlyBorrowerOperationsOrTrenBoxManager { _decreaseDebt(_borrower, _asset, _paybackFraction); } /** * Triggered when a debt is paid in full. */ function closeDebt( address _borrower, address _asset ) external override onlyBorrowerOperationsOrTrenBoxManager { _decreaseDebt(_borrower, _asset, 1 ether); } /** * Simulates the refund due -if- trenBox would be closed at this moment (helper function used by * the UI). */ function simulateRefund( address _borrower, address _asset, uint256 _paybackFraction ) external view override returns (uint256) { require(_paybackFraction <= 1 ether, "Payback fraction cannot be higher than 1 (@ 10**18)"); require(_paybackFraction != 0, "Payback fraction cannot be zero"); FeeRecord storage record = feeRecords[_borrower][_asset]; if (record.amount == 0 || record.to < block.timestamp) { return 0; } uint256 expiredAmount = _calcExpiredAmount(record.from, record.to, record.amount); if (_paybackFraction == 1e18) { // full payback return record.amount - expiredAmount; } else { // calc refund amount proportional to the payment return ((record.amount - expiredAmount) * _paybackFraction) / 1 ether; } } /** * Triggered when a trenBox is liquidated; in that case, all remaining fees are collected by the * platform, * and no refunds are generated. */ function liquidateDebt( address _borrower, address _asset ) external override onlyTrenBoxManager { FeeRecord memory mRecord = feeRecords[_borrower][_asset]; if (mRecord.amount != 0) { _closeExpiredOrLiquidatedFeeRecord(_borrower, _asset, mRecord.amount); } } /** * Batch collect fees from an array of borrowers/assets. */ function collectFees( address[] calldata _borrowers, address[] calldata _assets ) external override { uint256 borrowersLength = _borrowers.length; if (borrowersLength != _assets.length || borrowersLength == 0) { revert FeeCollector__ArrayMismatch(); } uint256 NOW = block.timestamp; for (uint256 i = 0; i < borrowersLength;) { address borrower = _borrowers[i]; address asset = _assets[i]; FeeRecord storage sRecord = feeRecords[borrower][asset]; uint256 expiredAmount = _calcExpiredAmount(sRecord.from, sRecord.to, sRecord.amount); if (expiredAmount > 0) { uint256 updatedAmount = sRecord.amount - expiredAmount; sRecord.amount = updatedAmount; sRecord.from = NOW; _collectFee(borrower, asset, expiredAmount); emit FeeRecordUpdated(borrower, asset, NOW, sRecord.to, updatedAmount); } unchecked { i++; } } } function handleRedemptionFee(address _asset, uint256 _amount) external onlyTrenBoxManager { if (IAdminContract(adminContract).getRouteToTRENStaking()) { ITRENStaking(trenStaking).increaseFeeAsset(_asset, _amount); } emit RedemptionFeeCollected(_asset, _amount); } function getProtocolRevenueDestination() public view override returns (address) { return IAdminContract(adminContract).getRouteToTRENStaking() ? trenStaking : treasuryAddress; } // Helper & internal methods // ---------------------------------------------------------------------------------------- function _decreaseDebt(address _borrower, address _asset, uint256 _paybackFraction) internal { uint256 NOW = block.timestamp; require(_paybackFraction <= 1 ether, "Payback fraction cannot be higher than 1 (@ 10**18)"); require(_paybackFraction != 0, "Payback fraction cannot be zero"); FeeRecord storage sRecord = feeRecords[_borrower][_asset]; if (sRecord.amount == 0) { return; } if (sRecord.to <= NOW) { _closeExpiredOrLiquidatedFeeRecord(_borrower, _asset, sRecord.amount); } else { // collect expired refund uint256 expiredAmount = _calcExpiredAmount(sRecord.from, sRecord.to, sRecord.amount); _collectFee(_borrower, _asset, expiredAmount); if (_paybackFraction == 1e18) { // on a full payback, there's no refund; refund amount is discounted from final // payment uint256 refundAmount = sRecord.amount - expiredAmount; IDebtToken(debtToken).burnFromWhitelistedContract(refundAmount); sRecord.amount = 0; emit FeeRecordUpdated(_borrower, _asset, NOW, 0, 0); } else { // refund amount proportional to the payment uint256 refundAmount = ((sRecord.amount - expiredAmount) * _paybackFraction) / 1 ether; _refundFee(_borrower, _asset, refundAmount); uint256 updatedAmount = sRecord.amount - expiredAmount - refundAmount; sRecord.amount = updatedAmount; sRecord.from = NOW; emit FeeRecordUpdated(_borrower, _asset, NOW, sRecord.to, updatedAmount); } } } function _createOrUpdateFeeRecord( address _borrower, address _asset, uint256 _feeAmount ) internal returns (uint256 feeToCollect) { FeeRecord storage sRecord = feeRecords[_borrower][_asset]; if (sRecord.amount == 0) { _createFeeRecord(_borrower, _asset, _feeAmount, sRecord); } else { if (sRecord.to <= block.timestamp) { feeToCollect = sRecord.amount; _createFeeRecord(_borrower, _asset, _feeAmount, sRecord); } else { feeToCollect = _updateFeeRecord(_borrower, _asset, _feeAmount, sRecord); } } } function _createFeeRecord( address _borrower, address _asset, uint256 _feeAmount, FeeRecord storage _sRecord ) internal { uint256 from = block.timestamp + MIN_FEE_DAYS * 1 days; uint256 to = from + FEE_EXPIRATION_SECONDS; _sRecord.amount = _feeAmount; _sRecord.from = from; _sRecord.to = to; emit FeeRecordUpdated(_borrower, _asset, from, to, _feeAmount); } function _updateFeeRecord( address _borrower, address _asset, uint256 _addedAmount, FeeRecord storage _sRecord ) internal returns (uint256) { uint256 NOW = block.timestamp; if (NOW < _sRecord.from) { // loan is still in its first week (MIN_FEE_DAYS) NOW = _sRecord.from; } uint256 expiredAmount = _calcExpiredAmount(_sRecord.from, _sRecord.to, _sRecord.amount); uint256 remainingAmount = _sRecord.amount - expiredAmount; uint256 remainingTime = _sRecord.to - NOW; uint256 updatedAmount = remainingAmount + _addedAmount; uint256 updatedTo = NOW + _calcNewDuration(remainingAmount, remainingTime, _addedAmount); _sRecord.amount = updatedAmount; _sRecord.from = NOW; _sRecord.to = updatedTo; emit FeeRecordUpdated(_borrower, _asset, NOW, updatedTo, updatedAmount); return expiredAmount; } function _closeExpiredOrLiquidatedFeeRecord( address _borrower, address _asset, uint256 _amount ) internal { _collectFee(_borrower, _asset, _amount); delete feeRecords[_borrower][_asset]; emit FeeRecordUpdated(_borrower, _asset, block.timestamp, 0, 0); } function _calcExpiredAmount( uint256 _from, uint256 _to, uint256 _amount ) internal view returns (uint256) { uint256 NOW = block.timestamp; if (_from > NOW) { return 0; } if (NOW >= _to) { return _amount; } uint256 PRECISION = 1e9; uint256 lifeTime = _to - _from; uint256 elapsedTime = NOW - _from; uint256 decayRate = (_amount * PRECISION) / lifeTime; uint256 expiredAmount = (elapsedTime * decayRate) / PRECISION; return expiredAmount; } function _calcNewDuration( uint256 remainingAmount, uint256 remainingTimeToLive, uint256 addedAmount ) internal pure returns (uint256) { uint256 prevWeight = remainingAmount * remainingTimeToLive; uint256 nextWeight = addedAmount * FEE_EXPIRATION_SECONDS; uint256 newDuration = (prevWeight + nextWeight) / (remainingAmount + addedAmount); return newDuration; } /** * Transfers collected (debt token) fees to either the treasury or the TRENStaking contract, * depending on a flag. */ function _collectFee(address _borrower, address _asset, uint256 _feeAmount) internal { if (_feeAmount != 0) { address destination = getProtocolRevenueDestination(); IERC20(debtToken).safeTransfer(destination, _feeAmount); if (IAdminContract(adminContract).getRouteToTRENStaking()) { ITRENStaking(trenStaking).increaseFeeDebtToken(_feeAmount); } emit FeeCollected(_borrower, _asset, destination, _feeAmount); } } function _refundFee(address _borrower, address _asset, uint256 _refundAmount) internal { if (_refundAmount != 0) { IERC20(debtToken).safeTransfer(_borrower, _refundAmount); emit FeeRefunded(_borrower, _asset, _refundAmount); } } // Modifiers // -------------------------------------------------------------------------------------------------------- modifier onlyBorrowerOperations() { if (msg.sender != borrowerOperations) { revert FeeCollector__BorrowerOperationsOnly(msg.sender, borrowerOperations); } _; } modifier onlyTrenBoxManager() { if (msg.sender != trenBoxManager) { revert FeeCollector__TrenBoxManagerOnly(msg.sender, trenBoxManager); } _; } modifier onlyBorrowerOperationsOrTrenBoxManager() { if (msg.sender != borrowerOperations && msg.sender != trenBoxManager) { revert FeeCollector__BorrowerOperationsOrTrenBoxManagerOnly( msg.sender, borrowerOperations, trenBoxManager ); } _; } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; import { IBorrowerOperations } from "./Interfaces/IBorrowerOperations.sol"; import { IFeeCollector } from "./Interfaces/IFeeCollector.sol"; import { IFlashLoan } from "./Interfaces/IFlashLoan.sol"; import { IFlashLoanReceiver } from "./Interfaces/IFlashLoanReceiver.sol"; import { ITrenBoxManager } from "./Interfaces/ITrenBoxManager.sol"; import { IUniswapRouterV3 } from "./Interfaces/IUniswapRouterV3.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; contract FlashLoan is IFlashLoan, ReentrancyGuardUpgradeable, OwnableUpgradeable, ConfigurableAddresses, UUPSUpgradeable { string public constant NAME = "FlashLoan"; uint256 public constant FEE_DENOMINATOR = 1000; IUniswapRouterV3 public swapRouter; address public stableCoin; bool public isSetupInitialized; function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } // ------------------------------------------ Set functions ----------------------------------- function setInternalAddresses(address _stableCoin, address _swapRouter) external onlyOwner { if (isSetupInitialized) revert FlashLoan__SetupIsInitialized(); if (_stableCoin == address(0) || _swapRouter == address(0)) { revert FlashLoan__ZeroAddresses(); } stableCoin = _stableCoin; swapRouter = IUniswapRouterV3(_swapRouter); isSetupInitialized = true; emit AddressesSet(_stableCoin, _swapRouter); } // ------------------------------------------ External functions ------------------------------ // Get a simple flash loan of trenUSD function flashLoan(uint256 _amount) external nonReentrant { if (IAdminContract(adminContract).getFlashLoanMinNetDebt() > _amount) { revert FlashLoan__AmountBeyondMin(); } if (IAdminContract(adminContract).getFlashLoanMaxNetDebt() < _amount) { revert FlashLoan__AmountBeyondMax(); } mintTokens(_amount); uint256 balanceBefore = IDebtToken(debtToken).balanceOf(address(this)); uint256 fee = calculateFee(_amount); IDebtToken(debtToken).transfer(msg.sender, _amount); IFlashLoanReceiver(msg.sender).executeOperation(_amount, fee, address(debtToken)); if (IDebtToken(debtToken).balanceOf(address(this)) < balanceBefore + fee) { revert FlashLoan__LoanIsNotRepayable(); } burnTokens(_amount); sendFeeToCollector(); emit FlashLoanExecuted(msg.sender, _amount, fee); } function flashLoanForRepay(address _asset) external nonReentrant { if (!IAdminContract(adminContract).getIsActive(_asset)) { revert FlashLoan__CollateralIsNotActive(); } uint256 debt = ITrenBoxManager(trenBoxManager).getTrenBoxDebt(_asset, msg.sender); uint256 gasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); uint256 refund = IFeeCollector(feeCollector).simulateRefund(msg.sender, _asset, 1 ether); uint256 netDebt = debt - gasCompensation - refund; mintTokens(netDebt); IDebtToken(debtToken).transfer(msg.sender, netDebt); uint256 fee = calculateFee(netDebt); IBorrowerOperations(borrowerOperations).closeTrenBox(_asset); // TODO: push borr address uint256 collAmountIn = IERC20(_asset).balanceOf(address(this)); uint256 debtTokensToGet = netDebt + fee; swapTokens(_asset, collAmountIn, debtTokensToGet); if (IDebtToken(debtToken).balanceOf(address(this)) < debtTokensToGet) { revert FlashLoan__LoanIsNotRepayable(); } burnTokens(netDebt); sendFeeToCollector(); emit FlashLoanExecuted(msg.sender, netDebt, fee); } function getFlashLoanRate() external view returns (uint256) { return IAdminContract(adminContract).getFlashLoanFee(); } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } // ------------------------------------------ Private functions ------------------------------- function calculateFee(uint256 _amount) private view returns (uint256) { uint256 _feeRate = IAdminContract(adminContract).getFlashLoanFee(); return (_amount * _feeRate) / FEE_DENOMINATOR; } function sendFeeToCollector() private { address collector = IFeeCollector(feeCollector).getProtocolRevenueDestination(); uint256 feeAmount = IDebtToken(debtToken).balanceOf(address(this)); IDebtToken(debtToken).transfer(collector, feeAmount); } function mintTokens(uint256 _amount) private { IDebtToken(debtToken).mintFromWhitelistedContract(_amount); } function burnTokens(uint256 _amount) private { IDebtToken(debtToken).burnFromWhitelistedContract(_amount); } function swapTokens(address _tokenIn, uint256 _collAmountIn, uint256 _debtAmountOut) private { // Approve swapRouter to spend amountInMaximum IERC20(_tokenIn).approve(address(swapRouter), _collAmountIn); // The tokenIn/tokenOut field is the shared token between the two pools used in the multiple // pool swap. In this case stable coin is the "shared" token. // For an exactOutput swap, the first swap that occurs is the swap which returns the // eventual desired token. // In this case, our desired output token is debtToken so that swap happpens first, and is // encoded in the path accordingly. IUniswapRouterV3.ExactOutputParams memory params = IUniswapRouterV3.ExactOutputParams({ path: abi.encodePacked(address(debtToken), uint24(3000), stableCoin, uint24(3000), _tokenIn), recipient: address(this), deadline: block.timestamp, amountOut: _debtAmountOut, amountInMaximum: _collAmountIn }); // Executes the swap, returning the amountIn actually spent. uint256 amountIn = swapRouter.exactOutput(params); // If the swap did not require the full _collAmountIn to achieve the exact amountOut then we // refund msg.sender and approve the router to spend 0. if (amountIn < _collAmountIn) { IERC20(_tokenIn).approve(address(swapRouter), 0); IERC20(_tokenIn).transfer(msg.sender, _collAmountIn - amountIn); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; contract GasPool is Ownable { // do nothing, as the core contracts have permission to send to and burn from this address string public constant NAME = "GasPool"; constructor(address initialOwner) Ownable(initialOwner) { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IPool } from "./IPool.sol"; interface IActivePool is IPool { event ActivePoolDebtUpdated(address _asset, uint256 _debtTokenAmount); event ActivePoolAssetBalanceUpdated(address _asset, uint256 _balance); error ActivePool__NotAuthorizedContract(); function sendAsset(address _asset, address _account, uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IAdminContract { // Structs // ---------------------------------------------------------------------------------------------------------- struct CollateralParams { uint256 index; // Maps to token address in validCollateral[] bool active; uint256 borrowingFee; uint256 ccr; uint256 mcr; uint256 debtTokenGasCompensation; // Amount of debtToken to be locked in gas pool on opening // trenBoxes uint256 minNetDebt; // Minimum amount of net debtToken a trenBox must have uint256 mintCap; uint256 percentDivisor; uint256 redemptionFeeFloor; uint256 redemptionBlockTimestamp; } struct FlashLoanParams { uint256 flashLoanFee; // 10 = 0,1%, 100 = 10% => 10 out of $1000 = $10 uint256 flashLoanMinDebt; // min amount of trenUSD to mint for Flash Loan uint256 flashLoanMaxDebt; // max amount of trenUSD to mint for Flash Loan } // Custom Errors // ---------------------------------------------------------------------------------------------------- error SafeCheckError(string parameter, uint256 valueEntered, uint256 minValue, uint256 maxValue); error AdminContract__OnlyOwner(); error AdminContract__OnlyTimelock(); error AdminContract__CollateralAlreadyInitialized(); error AdminContract__CollateralExists(); error AdminContract__CollateralDoesNotExist(); error AdminContract__CollateralNotConfigured(); // Events // ----------------------------------------------------------------------------------------------------------- event CollateralAdded(address _collateral); event MCRChanged(uint256 oldMCR, uint256 newMCR); event CCRChanged(uint256 oldCCR, uint256 newCCR); event MinNetDebtChanged(uint256 oldMinNet, uint256 newMinNet); event PercentDivisorChanged(uint256 oldPercentDiv, uint256 newPercentDiv); event BorrowingFeeChanged(uint256 oldBorrowingFee, uint256 newBorrowingFee); event RedemptionFeeFloorChanged(uint256 oldRedemptionFeeFloor, uint256 newRedemptionFeeFloor); event MintCapChanged(uint256 oldMintCap, uint256 newMintCap); event RedemptionBlockTimestampChanged(address _collateral, uint256 _blockTimestamp); event FlashLoanFeeChanged(uint256 oldFee, uint256 newFee); event FlashLoanMinDebtChanged(uint256 oldMinDebt, uint256 newMinDebt); event FlashLoanMaxDebtChanged(uint256 oldMaxDebt, uint256 newMaxDebt); // Functions // -------------------------------------------------------------------------------------------------------- function DECIMAL_PRECISION() external view returns (uint256); function _100pct() external view returns (uint256); function addNewCollateral(address _collateral, uint256 _debtTokenGasCompensation) external; function setCollateralParameters( address _collateral, uint256 borrowingFee, uint256 ccr, uint256 mcr, uint256 minNetDebt, uint256 mintCap, uint256 percentDivisor, uint256 redemptionFeeFloor ) external; function setMCR(address _collateral, uint256 newMCR) external; function setCCR(address _collateral, uint256 newCCR) external; function setMinNetDebt(address _collateral, uint256 minNetDebt) external; function setPercentDivisor(address _collateral, uint256 percentDivisor) external; function setBorrowingFee(address _collateral, uint256 borrowingFee) external; function setRedemptionFeeFloor(address _collateral, uint256 redemptionFeeFloor) external; function setMintCap(address _collateral, uint256 mintCap) external; function setRedemptionBlockTimestamp(address _collateral, uint256 _blockTimestamp) external; function switchRouteToTRENStaking() external; function getIndex(address _collateral) external view returns (uint256); function getIsActive(address _collateral) external view returns (bool); function getValidCollateral() external view returns (address[] memory); function getMcr(address _collateral) external view returns (uint256); function getCcr(address _collateral) external view returns (uint256); function getDebtTokenGasCompensation(address _collateral) external view returns (uint256); function getMinNetDebt(address _collateral) external view returns (uint256); function getPercentDivisor(address _collateral) external view returns (uint256); function getBorrowingFee(address _collateral) external view returns (uint256); function getRedemptionFeeFloor(address _collateral) external view returns (uint256); function getRedemptionBlockTimestamp(address _collateral) external view returns (uint256); function getMintCap(address _collateral) external view returns (uint256); function getTotalAssetDebt(address _asset) external view returns (uint256); function getFlashLoanFee() external view returns (uint256); function getFlashLoanMinNetDebt() external view returns (uint256); function getFlashLoanMaxNetDebt() external view returns (uint256); function getRouteToTRENStaking() external view returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IBorrowerOperations { // --- Enums --- enum BorrowerOperation { openTrenBox, closeTrenBox, adjustTrenBox } struct AdjustTrenBox { address asset; bool isCollIncrease; uint256 price; uint256 collChange; uint256 netDebtChange; uint256 debt; uint256 coll; uint256 oldICR; uint256 newICR; uint256 newTCR; uint256 debtTokenFee; uint256 newDebt; uint256 newColl; uint256 stake; } struct OpenTrenBox { address asset; uint256 price; uint256 debtTokenFee; uint256 netDebt; uint256 compositeDebt; uint256 ICR; uint256 NICR; uint256 stake; uint256 arrayIndex; } // --- Events --- event BorrowingFeePaid(address indexed _asset, address indexed _borrower, uint256 _feeAmount); event TrenBoxCreated(address indexed _asset, address indexed _borrower, uint256 arrayIndex); event TrenBoxUpdated( address indexed _asset, address indexed _borrower, uint256 _debt, uint256 _coll, uint256 stake, BorrowerOperation operation ); // --- Errors --- error BorrowerOperations__NotActiveColl(); error BorrowerOperations__TrenBoxNotExistOrClosed(); error BorrowerOperations__TrenBoxIsActive(); error BorrowerOperations__TrenBoxNetDebtLessThanMin(); error BorrowerOperations__CompositeDebtZero(); error BorrowerOperations__TrenBoxICRBelowCCR(); error BorrowerOperations__TrenBoxICRBelowMCR(); error BorrowerOperations__TrenBoxNewICRBelowOldICR(); error BorrowerOperations__TrenBoxNewTCRBelowCCR(); error BorrowerOperations__ZeroDebtChange(); error BorrowerOperations__NotSingularChange(); error BorrowerOperations__ZeroAdjustment(); error BorrowerOperations__OperationInRecoveryMode(); error BorrowerOperations__CollWithdrawalInRecoveryMode(); error BorrowerOperations__RepayLargerThanTrenBoxDebt(); error BorrowerOperations__InsufficientDebtBalance(); error BorrowerOperations__InsufficientCollateral(); error BorrowerOperations__ExceedMintCap(); // --- Functions --- function openTrenBox( address _asset, uint256 _assetAmount, uint256 _debtTokenAmount, address _upperHint, address _lowerHint ) external; function addColl( address _asset, uint256 _assetSent, address _upperHint, address _lowerHint ) external; function withdrawColl( address _asset, uint256 _assetAmount, address _upperHint, address _lowerHint ) external; function withdrawDebtTokens( address _asset, uint256 _debtTokenAmount, address _upperHint, address _lowerHint ) external; function repayDebtTokens( address _asset, uint256 _debtTokenAmount, address _upperHint, address _lowerHint ) external; function closeTrenBox(address _asset) external; function adjustTrenBox( address _asset, uint256 _assetSent, uint256 _collWithdrawal, uint256 _debtChange, bool isDebtIncrease, address _upperHint, address _lowerHint ) external; function claimCollateral(address _asset) external; function getCompositeDebt(address _asset, uint256 _debt) external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IDeposit } from "./IDeposit.sol"; interface ICollSurplusPool is IDeposit { // --- Errors --- error CollSurplusPool__NotBorrowerOperations(); error CollSurplusPool__NotTrenBoxManager(); error CollSurplusPool__NotActivePool(); error CollSurplusPool__NoClaimableColl(); // --- Events --- event CollBalanceUpdated(address indexed _account, uint256 _newBalance); event AssetSent(address _to, uint256 _amount); // --- Functions --- function getAssetBalance(address _asset) external view returns (uint256); function getCollateral(address _asset, address _account) external view returns (uint256); function accountSurplus(address _asset, address _account, uint256 _amount) external; function claimColl(address _asset, address _account) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface ICommunityIssuance { // --- Errors --- error CommunityIssuance__SetupAlreadyInitialized(); error CommunityIssuance__InvalidAddresses(); error CommunityIssuance__InvalidAdminContractAddress(); // --- Events --- event TotalTRENIssuedUpdated(uint256 _totalTRENIssued); // --- Functions --- function issueTREN() external returns (uint256); function sendTREN(address _account, uint256 _TRENamount) external; function addFundToStabilityPool(uint256 _assignedSupply) external; function addFundToStabilityPoolFrom(uint256 _assignedSupply, address _spender) external; function setWeeklyTrenDistribution(uint256 _weeklyReward) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IDebtToken is IERC20 { event TokenBalanceUpdated(address _user, uint256 _amount); event EmergencyStopMintingCollateral(address _asset, bool state); event WhitelistChanged(address _whitelisted, bool whitelisted); event ProtocolContractsAddressesSet( address borrowerOperations, address stabilityPool, address trenBoxManager ); error DebtToken__MintBlockedForCollateral(address collateral); error DebtToken__InvalidAddressToConnect(); error DebtToken__CannotTransferTokensToZeroAddress(); error DebtToken__CannotTransferTokensToTokenContract(); error DebtToken__NotWhitelistedContract(address notWhitelistedContract); error DebtToken__CallerIsNotBorrowerOperations(address caller); error DebtToken__CallerIsNotStabilityPool(address caller); error DebtToken__CannotBurnTokens(); error DebtToken__CannotReturnFromPool(); function emergencyStopMinting(address _asset, bool status) external; function mint(address _asset, address _account, uint256 _amount) external; function mintFromWhitelistedContract(uint256 _amount) external; function burnFromWhitelistedContract(uint256 _amount) external; function burn(address _account, uint256 _amount) external; function sendToPool(address _sender, address poolAddress, uint256 _amount) external; function returnFromPool(address poolAddress, address user, uint256 _amount) external; function addWhitelist(address _address) external; function removeWhitelist(address _address) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IPool } from "./IPool.sol"; interface IDefaultPool is IPool { // --- Errors --- error DefaultPool__NotActivePool(); error DefaultPool__NotTrenBoxManager(); // --- Events --- event DefaultPoolDebtUpdated(address _asset, uint256 _debt); event DefaultPoolAssetBalanceUpdated(address _asset, uint256 _balance); // --- Functions --- function sendAssetToActivePool(address _asset, uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IDeposit { function receivedERC20(address _asset, uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IERC20Decimals { function decimals() external view returns (uint8); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IFeeCollector { // Events // ----------------------------------------------------------------------------------------------------------- event FeeRecordUpdated( address borrower, address asset, uint256 from, uint256 to, uint256 amount ); event FeeCollected(address borrower, address asset, address collector, uint256 amount); event FeeRefunded(address borrower, address asset, uint256 amount); event RedemptionFeeCollected(address asset, uint256 amount); // Structs // ---------------------------------------------------------------------------------------------------------- struct FeeRecord { uint256 from; // timestamp in seconds uint256 to; // timestamp in seconds uint256 amount; // refundable fee amount } // Custom Errors // ---------------------------------------------------------------------------------------------------- error FeeCollector__ArrayMismatch(); error FeeCollector__BorrowerOperationsOnly(address sender, address expected); error FeeCollector__BorrowerOperationsOrTrenBoxManagerOnly( address sender, address expected1, address expected2 ); error FeeCollector__InvalidTRENStakingAddress(); error FeeCollector__TrenBoxManagerOnly(address sender, address expected); // Functions // -------------------------------------------------------------------------------------------------------- function increaseDebt(address _borrower, address _asset, uint256 _feeAmount) external; function decreaseDebt(address _borrower, address _asset, uint256 _paybackFraction) external; function closeDebt(address _borrower, address _asset) external; function liquidateDebt(address _borrower, address _asset) external; function simulateRefund( address _borrower, address _asset, uint256 _paybackFraction ) external returns (uint256); function collectFees(address[] calldata _borrowers, address[] calldata _assets) external; function handleRedemptionFee(address _asset, uint256 _amount) external; function getProtocolRevenueDestination() external view returns (address); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IFlashLoan { error FlashLoan__SetupIsInitialized(); error FlashLoan__ZeroAddresses(); error FlashLoan__LoanIsNotRepayable(); error FlashLoan__AmountBeyondMin(); error FlashLoan__AmountBeyondMax(); error FlashLoan__CollateralIsNotActive(); event FlashLoanExecuted( address indexed _borrower, uint256 indexed _debtAmount, uint256 _feeAmount ); event AddressesSet(address _stableCoin, address _swapRouter); function flashLoan(uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IFlashLoanReceiver { function executeOperation(uint256 _amount, uint256 _fee, address _debtToken) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IPool { // --- Events --- event AssetSent(address _to, address indexed _asset, uint256 _amount); // --- Functions --- function getAssetBalance(address _asset) external view returns (uint256); function getDebtTokenBalance(address _asset) external view returns (uint256); function increaseDebt(address _asset, uint256 _amount) external; function decreaseDebt(address _asset, uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; /** * @dev from * https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol */ interface ChainlinkAggregatorV3Interface { function decimals() external view returns (uint8); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); } interface IPriceFeed { // Enums // ---------------------------------------------------------------------------------------------------------- enum ProviderType { Chainlink, API3 } // Structs // -------------------------------------------------------------------------------------------------------- struct OracleRecord { address oracleAddress; ProviderType providerType; uint256 timeoutSeconds; uint256 decimals; bool isEthIndexed; } // Custom Errors // -------------------------------------------------------------------------------------------------- error PriceFeed__ExistingOracleRequired(); error PriceFeed__InvalidDecimalsError(); error PriceFeed__InvalidOracleResponseError(address token); error PriceFeed__TimelockOnlyError(); error PriceFeed__UnknownAssetError(); // Events // --------------------------------------------------------------------------------------------------------- event NewOracleRegistered( address token, address oracleAddress, bool isEthIndexed, bool isFallback ); // Functions // ------------------------------------------------------------------------------------------------------ function fetchPrice(address _token) external view returns (uint256); function setOracle( address _token, address _oracle, ProviderType _type, uint256 _timeoutSeconds, bool _isEthIndexed, bool _isFallback ) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface IPriceFeedL2 { // Custom Errors // -------------------------------------------------------------------------------------------------- error PriceFeedL2__SequencerDown(); error PriceFeedL2__SequencerGracePeriodNotOver(); error PriceFeedL2__SequencerZeroAddress(); // Events // ----------------------------------------------------------------------------------------------------------- event SequencerUptimeFeedUpdated(address _sequencerUptimeFeed); // Functions // ------------------------------------------------------------------------------------------------------ function setSequencerUptimeFeedAddress(address _sequencerUptimeFeedAddress) external; function fetchPrice(address _token) external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface ISortedTrenBoxes { // --- Events --- event NodeAdded(address indexed _asset, address _id, uint256 _NICR); event NodeRemoved(address indexed _asset, address _id); error SortedTrenBoxer__ListDoesNotContainNode(); error SortedTrenBoxes__ListAlreadyContainsNode(); error SortedTrenBoxes__IdCannotBeZeroAddress(); error SortedTrenBoxes__NICRMustBeGreaterThanZero(); error SortedTrenBoxes__CallerMustBeTrenBoxManager(); error SortedTrenBoxes__CallerMustBeBorrowerOperationsOrTrenBoxManager(); // --- Functions --- function insert( address _asset, address _id, uint256 _ICR, address _prevId, address _nextId ) external; function remove(address _asset, address _id) external; function reInsert( address _asset, address _id, uint256 _newICR, address _prevId, address _nextId ) external; function contains(address _asset, address _id) external view returns (bool); function isEmpty(address _asset) external view returns (bool); function getSize(address _asset) external view returns (uint256); function getFirst(address _asset) external view returns (address); function getLast(address _asset) external view returns (address); function getNext(address _asset, address _id) external view returns (address); function getPrev(address _asset, address _id) external view returns (address); function validInsertPosition( address _asset, uint256 _ICR, address _prevId, address _nextId ) external view returns (bool); function findInsertPosition( address _asset, uint256 _ICR, address _prevId, address _nextId ) external view returns (address, address); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IDeposit } from "./IDeposit.sol"; interface IStabilityPool is IDeposit { // --- Structs --- struct Snapshots { mapping(address => uint256) S; uint256 P; uint256 G; uint128 scale; uint128 epoch; } // --- Events --- event CommunityIssuanceAddressChanged(address newAddress); event DepositSnapshotUpdated(address indexed _depositor, uint256 _P, uint256 _G); event SystemSnapshotUpdated(uint256 _P, uint256 _G); event AssetSent(address _asset, address _to, uint256 _amount); event GainsWithdrawn( address indexed _depositor, address[] _collaterals, uint256[] _amounts, uint256 _debtTokenLoss ); event TRENPaidToDepositor(address indexed _depositor, uint256 _TREN); event StabilityPoolAssetBalanceUpdated(address _asset, uint256 _newBalance); event StabilityPoolDebtTokenBalanceUpdated(uint256 _newBalance); event StakeChanged(uint256 _newSystemStake, address _depositor); event UserDepositChanged(address indexed _depositor, uint256 _newDeposit); event ProductUpdated(uint256 _P); event SumUpdated(address _asset, uint256 _S, uint128 _epoch, uint128 _scale); event GainsUpdated(uint256 _G, uint128 _epoch, uint128 _scale); event EpochUpdated(uint128 _currentEpoch); event ScaleUpdated(uint128 _currentScale); // --- Errors --- error StabilityPool__ActivePoolOnly(address sender, address expected); error StabilityPool__AdminContractOnly(address sender, address expected); error StabilityPool__TrenBoxManagerOnly(address sender, address expected); error StabilityPool__ArrayNotInAscendingOrder(); error StabilityPool__DebtLossBelowOne(uint256 debtLoss); error StabilityPool__DebtLargerThanTotalDeposits(); error StabilityPool__ProductZero(); error StabilityPool__AssetsAndAmountsLengthMismatch(); error StabilityPool__UserHasNoDeposit(); error StabilityPool__AmountMustBeNonZero(); // --- Functions --- function addCollateralType(address _collateral) external; /* * Initial checks: * - _amount is not zero * --- * - Triggers a TREN issuance, based on time passed since the last issuance. The TREN issuance is shared between *all* depositors. * - Sends depositor's accumulated gains (TREN, assets) to depositor */ function provideToSP(uint256 _amount, address[] calldata _assets) external; /* * Initial checks: * - _amount is zero or there are no under collateralized trenBoxes left in the system * - User has a non zero deposit * --- * - Triggers a TREN issuance, based on time passed since the last issuance. The TREN issuance is shared between *all* depositors. * - Sends all depositor's accumulated gains (TREN, assets) to depositor * - Decreases deposit's stake, and takes new snapshots. * * If _amount > userDeposit, the user withdraws all of their compounded deposit. */ function withdrawFromSP(uint256 _amount, address[] calldata _assets) external; /* Initial checks: * - Caller is TrenBoxManager * --- * Cancels out the specified debt against the debt token contained in the Stability Pool (as far as possible) * and transfers the TrenBox's collateral from ActivePool to StabilityPool. * Only called by liquidation functions in the TrenBoxManager. */ function offset(uint256 _debt, address _asset, uint256 _coll) external; /* * Returns debt tokens held in the pool. Changes when users deposit/withdraw, and when TrenBox debt is offset. */ function getTotalDebtTokenDeposits() external view returns (uint256); /* * Calculates the asset gains earned by the deposit since its last snapshots were taken. */ function getDepositorGains( address _depositor, address[] calldata _assets ) external view returns (address[] memory, uint256[] memory); /* * Calculate the TREN gain earned by a deposit since its last snapshots were taken. */ function getDepositorTRENGain(address _depositor) external view returns (uint256); /* * Return the user's compounded deposits. */ function getCompoundedDebtTokenDeposits(address _depositor) external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface ITrenBoxManager { // Enums // ------------------------------------------------------------------------------------------------------------ enum Status { nonExistent, active, closedByOwner, closedByLiquidation, closedByRedemption } enum TrenBoxManagerOperation { applyPendingRewards, liquidateInNormalMode, liquidateInRecoveryMode, redeemCollateral } // Events // ----------------------------------------------------------------------------------------------------------- event BaseRateUpdated(address indexed _asset, uint256 _baseRate); event LastFeeOpTimeUpdated(address indexed _asset, uint256 _lastFeeOpTime); event TotalStakesUpdated(address indexed _asset, uint256 _newTotalStakes); event SystemSnapshotsUpdated( address indexed _asset, uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot ); event LTermsUpdated(address indexed _asset, uint256 _L_Coll, uint256 _L_Debt); event TrenBoxSnapshotsUpdated(address indexed _asset, uint256 _L_Coll, uint256 _L_Debt); event TrenBoxIndexUpdated(address indexed _asset, address _borrower, uint256 _newIndex); event TrenBoxUpdated( address indexed _asset, address indexed _borrower, uint256 _debt, uint256 _coll, uint256 _stake, TrenBoxManagerOperation _operation ); // Custom Errors // ---------------------------------------------------------------------------------------------------- error TrenBoxManager__FeeBiggerThanAssetDraw(); error TrenBoxManager__OnlyOneTrenBox(); error TrenBoxManager__OnlyTrenBoxManagerOperations(); error TrenBoxManager__OnlyBorrowerOperations(); error TrenBoxManager__OnlyTrenBoxManagerOperationsOrBorrowerOperations(); // Structs // ---------------------------------------------------------------------------------------------------------- struct TrenBox { uint256 debt; uint256 coll; uint256 stake; Status status; uint128 arrayIndex; } // Functions // -------------------------------------------------------------------------------------------------------- function executeFullRedemption(address _asset, address _borrower, uint256 _newColl) external; function executePartialRedemption( address _asset, address _borrower, uint256 _newDebt, uint256 _newColl, uint256 _newNICR, address _upperPartialRedemptionHint, address _lowerPartialRedemptionHint ) external; function getTrenBoxOwnersCount(address _asset) external view returns (uint256); function getTrenBoxFromTrenBoxOwnersArray( address _asset, uint256 _index ) external view returns (address); function getNominalICR(address _asset, address _borrower) external view returns (uint256); function getCurrentICR( address _asset, address _borrower, uint256 _price ) external view returns (uint256); function updateStakeAndTotalStakes( address _asset, address _borrower ) external returns (uint256); function updateTrenBoxRewardSnapshots(address _asset, address _borrower) external; function addTrenBoxOwnerToArray( address _asset, address _borrower ) external returns (uint256 index); function applyPendingRewards(address _asset, address _borrower) external; function getPendingAssetReward( address _asset, address _borrower ) external view returns (uint256); function getPendingDebtTokenReward( address _asset, address _borrower ) external view returns (uint256); function hasPendingRewards(address _asset, address _borrower) external view returns (bool); function getEntireDebtAndColl( address _asset, address _borrower ) external view returns ( uint256 debt, uint256 coll, uint256 pendingDebtTokenReward, uint256 pendingAssetReward ); function closeTrenBox(address _asset, address _borrower) external; function closeTrenBoxLiquidation(address _asset, address _borrower) external; function removeStake(address _asset, address _borrower) external; function getRedemptionRate(address _asset) external view returns (uint256); function getRedemptionRateWithDecay(address _asset) external view returns (uint256); function getRedemptionFeeWithDecay( address _asset, uint256 _assetDraw ) external view returns (uint256); function getBorrowingRate(address _asset) external view returns (uint256); function getBorrowingFee( address _asset, uint256 _debtTokenAmount ) external view returns (uint256); function getTrenBoxStatus(address _asset, address _borrower) external view returns (uint256); function getTrenBoxStake(address _asset, address _borrower) external view returns (uint256); function getTrenBoxDebt(address _asset, address _borrower) external view returns (uint256); function getTrenBoxColl(address _asset, address _borrower) external view returns (uint256); function setTrenBoxStatus(address _asset, address _borrower, uint256 num) external; function increaseTrenBoxColl( address _asset, address _borrower, uint256 _collIncrease ) external returns (uint256); function decreaseTrenBoxColl( address _asset, address _borrower, uint256 _collDecrease ) external returns (uint256); function increaseTrenBoxDebt( address _asset, address _borrower, uint256 _debtIncrease ) external returns (uint256); function decreaseTrenBoxDebt( address _asset, address _borrower, uint256 _collDecrease ) external returns (uint256); function getTCR(address _asset, uint256 _price) external view returns (uint256); function checkRecoveryMode(address _asset, uint256 _price) external returns (bool); function isValidFirstRedemptionHint( address _asset, address _firstRedemptionHint, uint256 _price ) external returns (bool); function updateBaseRateFromRedemption( address _asset, uint256 _assetDrawn, uint256 _price, uint256 _totalDebtTokenSupply ) external; function getRedemptionFee(address _asset, uint256 _assetDraw) external view returns (uint256); function finalizeRedemption( address _asset, address _receiver, uint256 _debtToRedeem, uint256 _fee, uint256 _totalRedemptionRewards ) external; function redistributeDebtAndColl( address _asset, uint256 _debt, uint256 _coll, uint256 _debtToOffset, uint256 _collToSendToStabilityPool ) external; function updateSystemSnapshots_excludeCollRemainder( address _asset, uint256 _collRemainder ) external; function movePendingTrenBoxRewardsToActivePool( address _asset, uint256 _debtTokenAmount, uint256 _assetAmount ) external; function isTrenBoxActive(address _asset, address _borrower) external view returns (bool); function sendGasCompensation( address _asset, address _liquidator, uint256 _debtTokenAmount, uint256 _assetAmount ) external; function getNetDebt(address _asset, uint256 _debt) external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { ITrenBoxManager } from "./ITrenBoxManager.sol"; interface ITrenBoxManagerOperations { // Structs // ---------------------------------------------------------------------------------------------------------- struct HintHelperLocalVars { address asset; uint256 debtTokenAmount; uint256 price; uint256 maxIterations; } // Events // ----------------------------------------------------------------------------------------------------------- event Redemption( address indexed _asset, uint256 _attemptedDebtAmount, uint256 _actualDebtAmount, uint256 _collSent, uint256 _collFee ); event Liquidation( address indexed _asset, uint256 _liquidatedDebt, uint256 _liquidatedColl, uint256 _collGasCompensation, uint256 _debtTokenGasCompensation ); event TrenBoxLiquidated( address indexed _asset, address indexed _borrower, uint256 _debt, uint256 _coll, ITrenBoxManager.TrenBoxManagerOperation _operation ); event RedemptionSoftenParamChanged(uint256 _redemptionSofteningParam); // Custom Errors // ---------------------------------------------------------------------------------------------------- error TrenBoxManagerOperations__InvalidArraySize(); error TrenBoxManagerOperations__EmptyAmount(); error TrenBoxManagerOperations__FeePercentOutOfBounds( uint256 lowerBoundary, uint256 upperBoundary ); error TrenBoxManagerOperations__InsufficientDebtTokenBalance(uint256 availableBalance); error TrenBoxManagerOperations__NothingToLiquidate(); error TrenBoxManagerOperations__OnlyTrenBoxManager(); error TrenBoxManagerOperations__RedemptionIsBlocked(); error TrenBoxManagerOperations__TCRMustBeAboveMCR(uint256 tcr, uint256 mcr); error TrenBoxManagerOperations__UnableToRedeemAnyAmount(); error TrenBoxManagerOperations__TrenBoxNotActive(); error TrenBoxManagerOperations__InvalidParam(); error TrenBoxManagerOperations__NotTimelock(); // Structs // ---------------------------------------------------------------------------------------------------------- struct RedemptionTotals { uint256 remainingDebt; uint256 totalDebtToRedeem; uint256 totalCollDrawn; uint256 collFee; uint256 collToSendToRedeemer; uint256 decayedBaseRate; uint256 price; uint256 totalDebtTokenSupplyAtStart; } struct SingleRedemptionValues { uint256 debtLot; uint256 collLot; bool cancelledPartial; } struct LiquidationTotals { uint256 totalCollInSequence; uint256 totalDebtInSequence; uint256 totalCollGasCompensation; uint256 totalDebtTokenGasCompensation; uint256 totalDebtToOffset; uint256 totalCollToSendToSP; uint256 totalDebtToRedistribute; uint256 totalCollToRedistribute; uint256 totalCollSurplus; } struct LiquidationValues { uint256 entireTrenBoxDebt; uint256 entireTrenBoxColl; uint256 collGasCompensation; uint256 debtTokenGasCompensation; uint256 debtToOffset; uint256 collToSendToSP; uint256 debtToRedistribute; uint256 collToRedistribute; uint256 collSurplus; } struct LocalVariables_InnerSingleLiquidateFunction { uint256 collToLiquidate; uint256 pendingDebtReward; uint256 pendingCollReward; } struct LocalVariables_OuterLiquidationFunction { uint256 price; uint256 debtTokenInStabPool; bool recoveryModeAtStart; uint256 liquidatedDebt; uint256 liquidatedColl; } struct LocalVariables_LiquidationSequence { uint256 remainingDebtTokenInStabPool; uint256 ICR; address user; bool backToNormalMode; uint256 entireSystemDebt; uint256 entireSystemColl; } // Functions // -------------------------------------------------------------------------------------------------------- function liquidate(address _asset, address _borrower) external; function liquidateTrenBoxes(address _asset, uint256 _n) external; function batchLiquidateTrenBoxes(address _asset, address[] memory _trenBoxArray) external; function redeemCollateral( address _asset, uint256 _debtTokenAmount, address _upperPartialRedemptionHint, address _lowerPartialRedemptionHint, address _firstRedemptionHint, uint256 _partialRedemptionHintNICR, uint256 _maxIterations, uint256 _maxFeePercentage ) external; function getRedemptionHints( address _asset, uint256 _debtTokenAmount, uint256 _price, uint256 _maxIterations ) external returns ( address firstRedemptionHint, uint256 partialRedemptionHintNICR, uint256 truncatedDebtTokenAmount ); function getApproxHint( address _asset, uint256 _CR, uint256 _numTrials, uint256 _inputRandomSeed ) external returns (address hintAddress, uint256 diff, uint256 latestRandomSeed); function computeNominalCR(uint256 _coll, uint256 _debt) external returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; interface ITRENStaking { struct Snapshot { mapping(address asset => uint256 feeAmount) assetsFeeSnapshot; uint256 debtTokenFeeSnapshot; } error TRENStaking__SetupAlreadyInitialized(); error TRENStaking__StakingOnPause(); error TRENStaking__InvalidAddresses(); error TRENStaking__InvalidAmount(uint256 zeroValue); error TRENStaking__OnlyFeeCollector(address caller, address expected); error TRENStaking__InvalidStakeAmount(uint256 zeroValue); event SentAssetFeeToTreasury(address indexed _asset, uint256 _amount); event StakeUpdated(address indexed _staker, uint256 _newStake); event StakingAssetGainWithdrawn( address indexed _staker, address indexed _asset, uint256 _assetGain ); event StakingDebtTokenGainWithdrawn(address indexed _staker, uint256 _debtTokenAmount); event AssetFeeUpdated(address indexed _asset, uint256 _amount); event TotalDebtTokenFeeUpdated(uint256 _amount); event TotalTRENStakedUpdated(uint256 _totalTRENStaked); event SentAsset(address indexed _asset, address indexed _account, uint256 _amount); event StakerSnapshotsUpdated(address _staker, uint256 _feeAsset, uint256 _feeDebtToken); function increaseFeeAsset(address _asset, uint256 _feeAsset) external; function increaseFeeDebtToken(uint256 _TRENFee) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; pragma abicoder v2; interface IUniswapRouterV3 { struct ExactOutputParams { bytes path; address recipient; uint256 deadline; uint256 amountOut; uint256 amountInMaximum; } function exactOutput(ExactOutputParams memory params) external returns (uint256 amountIn); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { IPriceFeed, ChainlinkAggregatorV3Interface } from "./Interfaces/IPriceFeed.sol"; import { API3ProxyInterface } from "./Pricing/API3ProxyInterface.sol"; /** * @title The PriceFeed contract contains a directory of oracles for fetching prices for assets * based on their addresses; * optionally fallback oracles can also be registered in case the primary source * fails or is stale. */ contract PriceFeed is IPriceFeed, OwnableUpgradeable, UUPSUpgradeable, ConfigurableAddresses { // Constants // -------------------------------------------------------------------------------------------------------- string public constant NAME = "PriceFeed"; /// @dev Used to convert an oracle price answer to an 18-digit precision uint uint256 public constant TARGET_DIGITS = 18; // State // ------------------------------------------------------------------------------------------------------------ mapping(address token => OracleRecord oracleRecord) public oracles; mapping(address token => OracleRecord oracleRecord) public fallbacks; // Initializer // ------------------------------------------------------------------------------------------------------ function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } // Admin routines // --------------------------------------------------------------------------------------------------- function setOracle( address _token, address _oracle, ProviderType _type, uint256 _timeoutSeconds, bool _isEthIndexed, bool _isFallback ) external override { _requireOwnerOrTimelock(_token, _isFallback); if (_isFallback && oracles[_token].oracleAddress == address(0)) { // fallback setup requires an existing primary oracle for the asset revert PriceFeed__ExistingOracleRequired(); } uint256 decimals = _fetchDecimals(_oracle, _type); if (decimals == 0) { revert PriceFeed__InvalidDecimalsError(); } OracleRecord memory newOracle = OracleRecord({ oracleAddress: _oracle, providerType: _type, timeoutSeconds: _timeoutSeconds, decimals: decimals, isEthIndexed: _isEthIndexed }); uint256 price = _fetchOracleScaledPrice(newOracle); if (price == 0) { revert PriceFeed__InvalidOracleResponseError(_token); } if (_isFallback) { fallbacks[_token] = newOracle; } else { oracles[_token] = newOracle; } emit NewOracleRegistered(_token, _oracle, _isEthIndexed, _isFallback); } // Public functions // ------------------------------------------------------------------------------------------------- /** * @notice Fetches the price for an asset from a previously configured oracle. * @dev Callers: * - BorrowerOperations.openTrenBox() * - BorrowerOperations.adjustTrenBox() * - BorrowerOperations.closeTrenBox() * - TrenBoxManagerOperations.liquidateTrenBoxes() * - TrenBoxManagerOperations.batchLiquidateTrenBoxes() * - TrenBoxManagerOperations.redeemCollateral() */ function fetchPrice(address _token) public view virtual returns (uint256) { // Tries fetching the price from the oracle OracleRecord memory oracle = oracles[_token]; uint256 price = _fetchOracleScaledPrice(oracle); if (price != 0) { return oracle.isEthIndexed ? _calcEthIndexedPrice(price) : price; } // If the oracle fails (and returns 0), try again with the fallback oracle = fallbacks[_token]; price = _fetchOracleScaledPrice(oracle); if (price != 0) { return oracle.isEthIndexed ? _calcEthIndexedPrice(price) : price; } revert PriceFeed__InvalidOracleResponseError(_token); } // Internal functions // ----------------------------------------------------------------------------------------------- function _fetchDecimals(address _oracle, ProviderType _type) internal view returns (uint8) { if (ProviderType.Chainlink == _type) { return ChainlinkAggregatorV3Interface(_oracle).decimals(); } else if (ProviderType.API3 == _type) { return 18; } return 0; } function _fetchOracleScaledPrice(OracleRecord memory oracle) internal view returns (uint256) { uint256 oraclePrice = 0; uint256 priceTimestamp = 0; if (oracle.oracleAddress == address(0)) { revert PriceFeed__UnknownAssetError(); } if (ProviderType.Chainlink == oracle.providerType) { (oraclePrice, priceTimestamp) = _fetchChainlinkOracleResponse(oracle.oracleAddress); } else if (ProviderType.API3 == oracle.providerType) { (oraclePrice, priceTimestamp) = _fetchAPI3OracleResponse(oracle.oracleAddress); } if (oraclePrice != 0 && !_isStalePrice(priceTimestamp, oracle.timeoutSeconds)) { return _scalePriceByDigits(oraclePrice, oracle.decimals); } return 0; } function _isStalePrice( uint256 _priceTimestamp, uint256 _oracleTimeoutSeconds ) internal view returns (bool) { return block.timestamp - _priceTimestamp > _oracleTimeoutSeconds; } function _fetchChainlinkOracleResponse(address _oracleAddress) internal view returns (uint256 price, uint256 timestamp) { try ChainlinkAggregatorV3Interface(_oracleAddress).latestRoundData() returns ( uint80 roundId, int256 answer, uint256, /* startedAt */ uint256 updatedAt, uint80 /* answeredInRound */ ) { if (roundId != 0 && updatedAt != 0 && answer != 0) { price = uint256(answer); timestamp = updatedAt; } } catch { // If call to Chainlink aggregator reverts, return a zero response price = 0; timestamp = 0; } } function _fetchAPI3OracleResponse(address _oracleAddress) internal view returns (uint256 price, uint256 timestamp) { (int224 _value, uint256 _timestamp) = API3ProxyInterface(_oracleAddress).read(); if (_value > 0) { /// @dev negative check -> see API3ProxyInterface price = uint256(int256(_value)); timestamp = _timestamp; } } /** * @dev Fetches the ETH:USD price (using the zero address as being the ETH asset), then * multiplies it by the indexed price. Assumes an oracle has been set for that purpose. */ function _calcEthIndexedPrice(uint256 _ethAmount) internal view returns (uint256) { uint256 ethPrice = fetchPrice(address(0)); return (ethPrice * _ethAmount) / 1 ether; } /** * @dev Scales oracle's response up/down to Gravita's target precision; returns unaltered price * if already on target digits. */ function _scalePriceByDigits( uint256 _price, uint256 _priceDigits ) internal pure returns (uint256) { unchecked { if (_priceDigits > TARGET_DIGITS) { return _price / (10 ** (_priceDigits - TARGET_DIGITS)); } else if (_priceDigits < TARGET_DIGITS) { return _price * (10 ** (TARGET_DIGITS - _priceDigits)); } } return _price; } // Access control functions // ----------------------------------------------------------------------------------------- /** * @dev Requires msg.sender to be the contract owner when the oracle is first set. Subsequent * updates need to come through the timelock contract. */ function _requireOwnerOrTimelock(address _token, bool _isFallback) internal view { OracleRecord storage record = _isFallback ? fallbacks[_token] : oracles[_token]; if (record.oracleAddress == address(0)) { _checkOwner(); } else if (msg.sender != timelockAddress) { revert PriceFeed__TimelockOnlyError(); } } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; /// @notice Interface for reading dAPI data feeds from https://market.api3.org/dapis /// @dev The proxy contracts are generalized to support most types of numerical /// data feeds. This means that the user of this proxy is expected to validate /// the read values according to the specific use-case. For example, `value` is /// a signed integer, yet it being negative may not make sense in the case that /// the data feed represents the spot price of an asset. In that case, the user /// is responsible with ensuring that `value` is not negative. /// In the case that the data feed is from a single source, `timestamp` is the /// system time of the Airnode when it signed the data. In the case that the /// data feed is from multiple sources, `timestamp` is the median of system /// times of the Airnodes when they signed the respective data. There are two /// points to consider while using `timestamp` in your contract logic: (1) It /// is based on the system time of the Airnodes, and not the block timestamp. /// This may be relevant when either of them drifts. (2) `timestamp` is an /// off-chain value that is being reported, similar to `value`. Both should /// only be trusted as much as the Airnode(s) that report them. /// /// from https://vscode.blockscan.com/arbitrum-one/0x26690F9f17FdC26D419371315bc17950a0FC90eD interface API3ProxyInterface { function read() external view returns (int224 value, uint32 timestamp); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; /** * @dev This contract was created to serve as a price feed for the bLUSD-USD pair, fixed at a 1:1 * rate. * Responses' roundId and updateTime will always be 2 minutes ago, while the previousRound will * be 5 min ago. */ contract FixedPriceAggregator is AggregatorV3Interface { uint8 private constant DECIMALS_VAL = 8; int256 private immutable PRICE; constructor(int256 _price) { PRICE = _price; } function decimals() external pure override returns (uint8) { return DECIMALS_VAL; } function description() external pure override returns (string memory) { return "FixedPriceAggregator"; } function getRoundData(uint80 _roundId) external view override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { uint256 timestamp = block.timestamp - 5 minutes; return (_roundId, PRICE, 0, timestamp, uint80(timestamp)); } function latestRoundData() external view override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { uint256 timestamp = block.timestamp - 2 minutes; return (uint80(timestamp), PRICE, 0, timestamp, uint80(timestamp)); } function version() external pure override returns (uint256) { return 1; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { ChainlinkAggregatorV3Interface } from "../Interfaces/IPriceFeed.sol"; import { IPriceFeedL2 } from "../Interfaces/IPriceFeedL2.sol"; import { PriceFeed } from "../PriceFeed.sol"; contract PriceFeedL2 is IPriceFeedL2, PriceFeed { // Constants // -------------------------------------------------------------------------------------------------------- /// @dev after sequencer comes back up, wait for up to X seconds for openTrenBox, adjustTrenBox /// & closeTrenBox uint256 public constant SEQUENCER_BORROWING_DELAY_SECONDS = 3600; /// @dev after sequencer comes back up, wait for up to X seconds for redemptions & liquidations uint256 public constant SEQUENCER_LIQUIDATION_DELAY_SECONDS = 7200; // State // ------------------------------------------------------------------------------------------------------------ address public sequencerUptimeFeedAddress; // Admin routines // --------------------------------------------------------------------------------------------------- /** * @dev Requires msg.sender to be the contract owner when the sequencer is first set. Subsequent * updates need to come through the timelock contract. */ function setSequencerUptimeFeedAddress(address _sequencerUptimeFeedAddress) external { if (_sequencerUptimeFeedAddress == address(0)) { revert PriceFeedL2__SequencerZeroAddress(); } if (sequencerUptimeFeedAddress == address(0)) { _checkOwner(); } else if (msg.sender != timelockAddress) { revert PriceFeed__TimelockOnlyError(); } sequencerUptimeFeedAddress = _sequencerUptimeFeedAddress; emit SequencerUptimeFeedUpdated(_sequencerUptimeFeedAddress); } // Public functions // ------------------------------------------------------------------------------------------------- /** * @dev Callers: * - BorrowerOperations.openTrenBox() * - BorrowerOperations.adjustTrenBox() * - BorrowerOperations.closeTrenBox() * - TrenBoxManagerOperations.liquidateTrenBoxes() * - TrenBoxManagerOperations.batchLiquidateTrenBoxes() * - TrenBoxManagerOperations.redeemCollateral() */ function fetchPrice(address _token) public view override(IPriceFeedL2, PriceFeed) returns (uint256) { _checkSequencerUptimeFeed(); return super.fetchPrice(_token); } // Internal functions // ----------------------------------------------------------------------------------------------- function _checkSequencerUptimeFeed() internal view { if (sequencerUptimeFeedAddress != address(0)) { // prettier-ignore ( /* uint80 roundId */ , int256 answer, /* uint256 startedAt */ , uint256 updatedAt, /* uint80 answeredInRound */ ) = ChainlinkAggregatorV3Interface(sequencerUptimeFeedAddress).latestRoundData(); // answer == 0 -> sequencer is up // answer == 1 -> sequencer is down bool isSequencerUp = answer == 0; if (!isSequencerUp) { revert PriceFeedL2__SequencerDown(); } uint256 delay; if (msg.sender == trenBoxManagerOperations) { // TrenBoxManagerOperations triggers liquidations and redemptions delay = SEQUENCER_LIQUIDATION_DELAY_SECONDS; } else { delay = SEQUENCER_BORROWING_DELAY_SECONDS; } uint256 timeSinceSequencerUp = block.timestamp - updatedAt; if (timeSinceSequencerUp <= delay) { revert PriceFeedL2__SequencerGracePeriodNotOver(); } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; /** * @notice Returns the ETH price for 1 sfrxETH by multiplying the results from the sfrxETH:frxETH * and frxETH:ETH feeds. * Needs to be multiplied by the ETH:USD feed for the final price. */ contract SfrxEth2EthPriceAggregator is AggregatorV3Interface { error NotImplementedException(); error sfrxEthToFrxEthZeroPrice(); error frxEthToEthZeroPrice(); int256 internal constant PRECISION = 1 ether; AggregatorV3Interface public constant sfrxEth2FrxEthAggregator = AggregatorV3Interface(0x98E5a52fB741347199C08a7a3fcF017364284431); AggregatorV3Interface public constant frxEth2EthAggregator = AggregatorV3Interface(0x5C3e80763862CB777Aa07BDDBcCE0123104e1c34); // AggregatorV3Interface functions // ---------------------------------------------------------------------------------- function decimals() external pure override returns (uint8) { // both (unupgradeable) source aggregators use 18 decimals return 18; } function description() external pure override returns (string memory) { return "SfrxEth2EthPriceAggregator"; } function getRoundData( uint80 // roundId ) external pure virtual override returns ( uint80, // roundId, int256, // answer, uint256, // startedAt, uint256, // updatedAt, uint80 // answeredInRound ) { // nondeterministic as there are two sources with different round ids revert NotImplementedException(); } function latestRoundData() external view override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { ( uint80 roundId1, int256 answer1, uint256 startedAt1, uint256 updatedAt1, uint80 answeredInRound1 ) = sfrxEth2FrxEthAggregator.latestRoundData(); if (answer1 == 0) { revert sfrxEthToFrxEthZeroPrice(); } ( uint80 roundId2, int256 answer2, uint256 startedAt2, uint256 updatedAt2, uint80 answeredInRound2 ) = frxEth2EthAggregator.latestRoundData(); if (answer2 == 0) { revert frxEthToEthZeroPrice(); } answer = (answer1 * answer2) / PRECISION; // for the round/time-related values, return the "oldest" roundId = roundId1 < roundId2 ? roundId1 : roundId2; startedAt = startedAt1 < startedAt2 ? startedAt1 : startedAt2; updatedAt = updatedAt1 < updatedAt2 ? updatedAt1 : updatedAt2; answeredInRound = answeredInRound1 < answeredInRound2 ? answeredInRound1 : answeredInRound2; } function version() external pure override returns (uint256) { return 1; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; /** * @dev Based on https://github.com/lidofinance/lido-dao/blob/master/contracts/0.6.12/WstETH.sol */ interface IWstETH { function stEthPerToken() external view returns (uint256); } /** * @notice Returns the USD price for 1 wstETH. * * @dev Queries the wstETH token for its stETH value/rate; then queries the stETH:USD oracle for the * price, and multiplies the results. * There is a known (minor) issue with the getRoundData() function, where the historical * value for a previous round (price) can be queried from the feed, but the current st/wstEth * rate is used (instead of the historical pair); * we do not see that as a problem as this contract's return values are * supposed to be used in short-time context checks (and not for long-term * single-source-of-truth queries) */ contract WstEth2UsdPriceAggregator is AggregatorV3Interface { error stEthZeroPrice(); error stEthPerTokenZero(); int256 internal constant PRECISION = 1 ether; IWstETH public immutable wstETH; AggregatorV3Interface public immutable stETH2USDAggregator; constructor(address _wstETHAddress, address _stETH2USDAggregatorAddress) { wstETH = IWstETH(_wstETHAddress); stETH2USDAggregator = AggregatorV3Interface(_stETH2USDAggregatorAddress); } // AggregatorV3Interface functions // ---------------------------------------------------------------------------------- function decimals() external view override returns (uint8) { return stETH2USDAggregator.decimals(); } function description() external pure override returns (string memory) { return "WstEth2UsdPriceAggregator"; } function getRoundData(uint80 _roundId) external view override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { (roundId, answer, startedAt, updatedAt, answeredInRound) = stETH2USDAggregator.getRoundData(_roundId); answer = _stETH2wstETH(answer); } function latestRoundData() external view override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { (roundId, answer, startedAt, updatedAt, answeredInRound) = stETH2USDAggregator.latestRoundData(); answer = _stETH2wstETH(answer); } function version() external pure override returns (uint256) { return 1; } // Internal/Helper functions // ---------------------------------------------------------------------------------------- function _stETH2wstETH(int256 stETHValue) internal view returns (int256) { if (stETHValue == 0) { revert stEthZeroPrice(); } int256 multiplier = int256(wstETH.stEthPerToken()); if (multiplier == 0) { revert stEthPerTokenZero(); } // wstETH.stEthPerToken() response has 18-digit precision, hence we need the denominator // below return (stETHValue * multiplier) / PRECISION; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ConfigurableAddresses } from "./Dependencies/ConfigurableAddresses.sol"; import { ISortedTrenBoxes } from "./Interfaces/ISortedTrenBoxes.sol"; import { ITrenBoxManager } from "./Interfaces/ITrenBoxManager.sol"; /* * A sorted doubly linked list with nodes sorted in descending order. * * Nodes map to active TrenBoxes in the system - the ID property is the address of a TrenBox owner. * Nodes are ordered according to their current nominal individual collateral ratio (NICR), * which is like the ICR but without the price, i.e., just collateral / debt. * * The list optionally accepts insert position hints. * * NICRs are computed dynamically at runtime, and not stored on the Node. This is because NICRs of active TrenBoxes * change dynamically as liquidation events occur. * * The list relies on the fact that liquidation events preserve ordering: a liquidation decreases the NICRs of all active TrenBoxes, * but maintains their order. A node inserted based on current NICR will maintain the correct position, * relative to it's peers, as rewards accumulate, as long as it's raw collateral and debt have not * changed. * Thus, Nodes remain sorted by current NICR. * * Nodes need only be re-inserted upon a TrenBox operation - when the owner adds or removes collateral or debt * to their position. * * The list is a modification of the following audited SortedDoublyLinkedList: * https://github.com/livepeer/protocol/blob/master/contracts/libraries/SortedDoublyLL.sol * * * Changes made in the Gravita implementation: * * - Keys have been removed from nodes * * - Ordering checks for insertion are performed by comparing an NICR argument to the current NICR, calculated at runtime. * The list relies on the property that ordering by ICR is maintained as the ETH:USD price varies. * * - Public functions with parameters have been made internal to save gas, and given an external wrapper function for external access */ contract SortedTrenBoxes is OwnableUpgradeable, UUPSUpgradeable, ISortedTrenBoxes, ConfigurableAddresses { string public constant NAME = "SortedTrenBoxes"; struct Node { bool exists; address nextId; // Id of next node (smaller NICR) in the list address prevId; // Id of previous node (larger NICR) in the list } struct TrenBoxesList { address head; // Head of the list. Also the node in the list with the largest NICR address tail; // Tail of the list. Also the node in the list with the smallest NICR uint256 size; // Current size of the list mapping(address depositor => Node node) nodes; // Track the corresponding ids for each node // in the list } mapping(address collateral => TrenBoxesList orderedList) public trenBoxes; modifier onlyTrenBoxManager() { if (msg.sender != trenBoxManager) { revert SortedTrenBoxes__CallerMustBeTrenBoxManager(); } _; } modifier onlyBorrowerOperationsOrTrenBoxManager() { if (msg.sender != borrowerOperations && msg.sender != trenBoxManager) { revert SortedTrenBoxes__CallerMustBeBorrowerOperationsOrTrenBoxManager(); } _; } modifier hasNonZeroId(address _id) { if (_id == address(0)) { revert SortedTrenBoxes__IdCannotBeZeroAddress(); } _; } modifier hasPositiveNICR(uint256 _NICR) { if (_NICR == 0) { revert SortedTrenBoxes__NICRMustBeGreaterThanZero(); } _; } // --- Initializer --- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); } /* * @dev Add a node to the list * @param _id Node's id * @param _NICR Node's NICR * @param _prevId Id of previous node for the insert position * @param _nextId Id of next node for the insert position */ function insert( address _asset, address _id, uint256 _NICR, address _prevId, address _nextId ) external override onlyBorrowerOperationsOrTrenBoxManager { _insert(_asset, _id, _NICR, _prevId, _nextId); } function _insert( address _asset, address _id, uint256 _NICR, address _prevId, address _nextId ) internal hasNonZeroId(_id) hasPositiveNICR(_NICR) { TrenBoxesList storage assetData = trenBoxes[_asset]; if (_contains(assetData, _id)) { revert SortedTrenBoxes__ListAlreadyContainsNode(); } address prevId = _prevId; address nextId = _nextId; if (!_validInsertPosition(_asset, _NICR, prevId, nextId)) { // Sender's hint was not a valid insert position // Use sender's hint to find a valid insert position (prevId, nextId) = _findInsertPosition(_asset, _NICR, prevId, nextId); } Node storage node = assetData.nodes[_id]; node.exists = true; if (prevId == address(0) && nextId == address(0)) { // Insert as head and tail assetData.head = _id; assetData.tail = _id; } else if (prevId == address(0)) { // Insert before `prevId` as the head node.nextId = assetData.head; assetData.nodes[assetData.head].prevId = _id; assetData.head = _id; } else if (nextId == address(0)) { // Insert after `nextId` as the tail node.prevId = assetData.tail; assetData.nodes[assetData.tail].nextId = _id; assetData.tail = _id; } else { // Insert at insert position between `prevId` and `nextId` node.nextId = nextId; node.prevId = prevId; assetData.nodes[prevId].nextId = _id; assetData.nodes[nextId].prevId = _id; } assetData.size = assetData.size + 1; emit NodeAdded(_asset, _id, _NICR); } function remove(address _asset, address _id) external override onlyTrenBoxManager { _remove(_asset, _id); } /* * @dev Remove a node from the list * @param _id Node's id */ function _remove(address _asset, address _id) internal { TrenBoxesList storage assetData = trenBoxes[_asset]; if (!_contains(assetData, _id)) { revert SortedTrenBoxer__ListDoesNotContainNode(); } Node storage node = assetData.nodes[_id]; if (assetData.size > 1) { // List contains more than a single node if (_id == assetData.head) { // The removed node is the head // Set head to next node assetData.head = node.nextId; // Set prev pointer of new head to null assetData.nodes[assetData.head].prevId = address(0); } else if (_id == assetData.tail) { // The removed node is the tail // Set tail to previous node assetData.tail = node.prevId; // Set next pointer of new tail to null assetData.nodes[assetData.tail].nextId = address(0); } else { // The removed node is neither the head nor the tail // Set next pointer of previous node to the next node assetData.nodes[node.prevId].nextId = node.nextId; // Set prev pointer of next node to the previous node assetData.nodes[node.nextId].prevId = node.prevId; } } else { // List contains a single node // Set the head and tail to null assetData.head = address(0); assetData.tail = address(0); } delete assetData.nodes[_id]; assetData.size = assetData.size - 1; emit NodeRemoved(_asset, _id); } /* * @dev Re-insert the node at a new position, based on its new NICR * @param _id Node's id * @param _newNICR Node's new NICR * @param _prevId Id of previous node for the new insert position * @param _nextId Id of next node for the new insert position */ function reInsert( address _asset, address _id, uint256 _newNICR, address _prevId, address _nextId ) external override onlyBorrowerOperationsOrTrenBoxManager { if (!contains(_asset, _id)) { revert SortedTrenBoxer__ListDoesNotContainNode(); } if (_newNICR == 0) { revert SortedTrenBoxes__NICRMustBeGreaterThanZero(); } _remove(_asset, _id); _insert(_asset, _id, _newNICR, _prevId, _nextId); } /* * @dev Checks if the list contains a node */ function contains(address _asset, address _id) public view override returns (bool) { return trenBoxes[_asset].nodes[_id].exists; } function _contains( TrenBoxesList storage _dataAsset, address _id ) internal view returns (bool) { return _dataAsset.nodes[_id].exists; } /* * @dev Checks if the list is empty */ function isEmpty(address _asset) public view override returns (bool) { return trenBoxes[_asset].size == 0; } /* * @dev Returns the current size of the list */ function getSize(address _asset) external view override returns (uint256) { return trenBoxes[_asset].size; } /* * @dev Returns the first node in the list (node with the largest NICR) */ function getFirst(address _asset) external view override returns (address) { return trenBoxes[_asset].head; } /* * @dev Returns the last node in the list (node with the smallest NICR) */ function getLast(address _asset) external view override returns (address) { return trenBoxes[_asset].tail; } /* * @dev Returns the next node (with a smaller NICR) in the list for a given node * @param _id Node's id */ function getNext(address _asset, address _id) external view override returns (address) { return trenBoxes[_asset].nodes[_id].nextId; } /* * @dev Returns the previous node (with a larger NICR) in the list for a given node * @param _id Node's id */ function getPrev(address _asset, address _id) external view override returns (address) { return trenBoxes[_asset].nodes[_id].prevId; } /* * @dev Check if a pair of nodes is a valid insertion point for a new node with the given NICR * @param _NICR Node's NICR * @param _prevId Id of previous node for the insert position * @param _nextId Id of next node for the insert position */ function validInsertPosition( address _asset, uint256 _NICR, address _prevId, address _nextId ) external view override returns (bool) { return _validInsertPosition(_asset, _NICR, _prevId, _nextId); } function _validInsertPosition( address _asset, uint256 _NICR, address _prevId, address _nextId ) internal view returns (bool) { if (_prevId == address(0) && _nextId == address(0)) { // `(null, null)` is a valid insert position if the list is empty return isEmpty(_asset); } else if (_prevId == address(0)) { // `(null, _nextId)` is a valid insert position if `_nextId` is the head of the list return trenBoxes[_asset].head == _nextId && _NICR >= ITrenBoxManager(trenBoxManager).getNominalICR(_asset, _nextId); } else if (_nextId == address(0)) { // `(_prevId, null)` is a valid insert position if `_prevId` is the tail of the list return trenBoxes[_asset].tail == _prevId && _NICR <= ITrenBoxManager(trenBoxManager).getNominalICR(_asset, _prevId); } else { // `(_prevId, _nextId)` is a valid insert position if they are adjacent nodes and // `_NICR` falls between the two nodes' NICRs return trenBoxes[_asset].nodes[_prevId].nextId == _nextId && ITrenBoxManager(trenBoxManager).getNominalICR(_asset, _prevId) >= _NICR && _NICR >= ITrenBoxManager(trenBoxManager).getNominalICR(_asset, _nextId); } } /* * @dev Descend the list (larger NICRs to smaller NICRs) to find a valid insert position * @param _trenBoxManager TrenBoxManager contract, passed in as param to save SLOAD’s * @param _NICR Node's NICR * @param _startId Id of node to start descending the list from */ function _descendList( address _asset, uint256 _NICR, address _startId ) internal view returns (address, address) { TrenBoxesList storage assetData = trenBoxes[_asset]; // If `_startId` is the head, check if the insert position is before the head if ( assetData.head == _startId && _NICR >= ITrenBoxManager(trenBoxManager).getNominalICR(_asset, _startId) ) { return (address(0), _startId); } address prevId = _startId; address nextId = assetData.nodes[prevId].nextId; // Descend the list until we reach the end or until we find a valid insert position while (prevId != address(0) && !_validInsertPosition(_asset, _NICR, prevId, nextId)) { prevId = assetData.nodes[prevId].nextId; nextId = assetData.nodes[prevId].nextId; } return (prevId, nextId); } /* * @dev Ascend the list (smaller NICRs to larger NICRs) to find a valid insert position * @param _trenBoxManager TrenBoxManager contract, passed in as param to save SLOAD’s * @param _NICR Node's NICR * @param _startId Id of node to start ascending the list from */ function _ascendList( address _asset, uint256 _NICR, address _startId ) internal view returns (address, address) { TrenBoxesList storage assetData = trenBoxes[_asset]; // If `_startId` is the tail, check if the insert position is after the tail if ( assetData.tail == _startId && _NICR <= ITrenBoxManager(trenBoxManager).getNominalICR(_asset, _startId) ) { return (_startId, address(0)); } address nextId = _startId; address prevId = assetData.nodes[nextId].prevId; // Ascend the list until we reach the end or until we find a valid insertion point while (nextId != address(0) && !_validInsertPosition(_asset, _NICR, prevId, nextId)) { nextId = assetData.nodes[nextId].prevId; prevId = assetData.nodes[nextId].prevId; } return (prevId, nextId); } /* * @dev Find the insert position for a new node with the given NICR * @param _NICR Node's NICR * @param _prevId Id of previous node for the insert position * @param _nextId Id of next node for the insert position */ function findInsertPosition( address _asset, uint256 _NICR, address _prevId, address _nextId ) external view override returns (address, address) { return _findInsertPosition(_asset, _NICR, _prevId, _nextId); } function _findInsertPosition( address _asset, uint256 _NICR, address _prevId, address _nextId ) internal view returns (address, address) { address prevId = _prevId; address nextId = _nextId; if (prevId != address(0)) { if ( !contains(_asset, prevId) || _NICR > ITrenBoxManager(trenBoxManager).getNominalICR(_asset, prevId) ) { // `prevId` does not exist anymore or now has a smaller NICR than the given NICR prevId = address(0); } } if (nextId != address(0)) { if ( !contains(_asset, nextId) || _NICR < ITrenBoxManager(trenBoxManager).getNominalICR(_asset, nextId) ) { // `nextId` does not exist anymore or now has a larger NICR than the given NICR nextId = address(0); } } if (prevId == address(0) && nextId == address(0)) { // No hint - descend list starting from head return _descendList(_asset, _NICR, trenBoxes[_asset].head); } else if (prevId == address(0)) { // No `prevId` for hint - ascend list starting from `nextId` return _ascendList(_asset, _NICR, nextId); } else if (nextId == address(0)) { // No `nextId` for hint - descend list starting from `prevId` return _descendList(_asset, _NICR, prevId); } else { // Descend list starting from `prevId` return _descendList(_asset, _NICR, prevId); } } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { TrenBase } from "./Dependencies/TrenBase.sol"; import { TrenMath, DECIMAL_PRECISION } from "./Dependencies/TrenMath.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; import { IActivePool } from "./Interfaces/IActivePool.sol"; import { IStabilityPool } from "./Interfaces/IStabilityPool.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; import { ICommunityIssuance } from "./Interfaces/ICommunityIssuance.sol"; /** * @title The Stability Pool holds debt tokens deposited by Stability Pool depositors. * @dev When a trenBox is liquidated, then depending on system conditions, some of its debt tokens * debt gets offset with * debt tokens in the Stability Pool: that is, the offset debt evaporates, and an equal amount of * debt tokens tokens in the Stability Pool is burned. * * Thus, a liquidation causes each depositor to receive a debt tokens loss, in proportion to their * deposit as a share of total deposits. * They also receive an Collateral gain, as the amount of collateral of the liquidated trenBox is * distributed among Stability depositors, * in the same proportion. * * When a liquidation occurs, it depletes every deposit by the same fraction: for example, a * liquidation that depletes 40% * of the total debt tokens in the Stability Pool, depletes 40% of each deposit. * * A deposit that has experienced a series of liquidations is termed a "compounded deposit": each * liquidation depletes the deposit, * multiplying it by some factor in range ]0,1[ * * * --- IMPLEMENTATION --- * * We use a highly scalable method of tracking deposits and Collateral gains that has O(1) * complexity. * * When a liquidation occurs, rather than updating each depositor's deposit and Collateral gain, we * simply update two state variables: * a product P, and a sum S. These are kept track for each type of collateral. * * A mathematical manipulation allows us to factor out the initial deposit, and accurately track all * depositors' compounded deposits * and accumulated Collateral amount gains over time, as liquidations occur, using just these two * variables P and S. When depositors join the * Stability Pool, they get a snapshot of the latest P and S: P_t and S_t, respectively. * * The formula for a depositor's accumulated Collateral amount gain is derived here: * https://github.com/liquity/dev/blob/main/papers/Scalable_Reward_Distribution_with_Compounding_Stakes.pdf * * For a given deposit d_t, the ratio P/P_t tells us the factor by which a deposit has decreased * since it joined the Stability Pool, * and the term d_t * (S - S_t)/P_t gives us the deposit's total accumulated Collateral amount gain. * * Each liquidation updates the product P and sum S. After a series of liquidations, a compounded * deposit and corresponding Collateral amount gain * can be calculated using the initial deposit, the depositor’s snapshots of P and S, and the * latest values of P and S. * * Any time a depositor updates their deposit (withdrawal, top-up) their accumulated Collateral * amount gain is paid out, their new deposit is recorded * (based on their latest compounded deposit and modified by the withdrawal/top-up), and they * receive new snapshots of the latest P and S. * Essentially, they make a fresh deposit that overwrites the old one. * * * --- SCALE FACTOR --- * * Since P is a running product in range ]0,1] that is always-decreasing, it should never reach 0 * when multiplied by a number in range ]0,1[. * Unfortunately, Solidity floor division always reaches 0, sooner or later. * * A series of liquidations that nearly empty the Pool (and thus each multiply P by a very small * number in range ]0,1[ ) may push P * to its 18 digit decimal limit, and round it to 0, when in fact the Pool hasn't been emptied: this * would break deposit tracking. * * So, to track P accurately, we use a scale factor: if a liquidation would cause P to decrease to * <1e-9 (and be rounded to 0 by Solidity), * we first multiply P by 1e9, and increment a currentScale factor by 1. * * The added benefit of using 1e9 for the scale factor (rather than 1e18) is that it ensures * negligible precision loss close to the * scale boundary: when P is at its minimum value of 1e9, the relative precision loss in P due to * floor division is only on the * order of 1e-9. * * --- EPOCHS --- * * Whenever a liquidation fully empties the Stability Pool, all deposits should become 0. However, * setting P to 0 would make P be 0 * forever, and break all future reward calculations. * * So, every time the Stability Pool is emptied by a liquidation, we reset P = 1 and currentScale = * 0, and increment the currentEpoch by 1. * * --- TRACKING DEPOSIT OVER SCALE CHANGES AND EPOCHS --- * * When a deposit is made, it gets snapshots of the currentEpoch and the currentScale. * * When calculating a compounded deposit, we compare the current epoch to the deposit's epoch * snapshot. If the current epoch is newer, * then the deposit was present during a pool-emptying liquidation, and necessarily has been * depleted to 0. * * Otherwise, we then compare the current scale to the deposit's scale snapshot. If they're equal, * the compounded deposit is given by d_t * P/P_t. * If it spans one scale change, it is given by d_t * P/(P_t * 1e9). If it spans more than one scale * change, we define the compounded deposit * as 0, since it is now less than 1e-9'th of its initial value (e.g. a deposit of 1 billion debt * tokens has depleted to < 1 debt token). * * * --- TRACKING DEPOSITOR'S COLLATERAL AMOUNT GAIN OVER SCALE CHANGES AND EPOCHS --- * * In the current epoch, the latest value of S is stored upon each scale change, and the mapping * (scale -> S) is stored for each epoch. * * This allows us to calculate a deposit's accumulated Collateral amount gain, during the epoch in * which the deposit was non-zero and earned Collateral amount. * * We calculate the depositor's accumulated Collateral amount gain for the scale at which they made * the deposit, using the Collateral amount gain formula: * e_1 = d_t * (S - S_t) / P_t * * and also for scale after, taking care to divide the latter by a factor of 1e9: * e_2 = d_t * S / (P_t * 1e9) * * The gain in the second scale will be full, as the starting point was in the previous scale, thus * no need to subtract anything. * The deposit therefore was present for reward events from the beginning of that second scale. * * S_i-S_t + S_{i+1} * .<--------.------------> * . . * . S_i . S_{i+1} * <--.-------->.<-----------> * S_t. . * <->. . * t . * |---+---------|-------------|-----... * i i+1 * * The sum of (e_1 + e_2) captures the depositor's total accumulated Collateral amount gain, * handling the case where their * deposit spanned one scale change. We only care about gains across one scale change, since the * compounded * deposit is defined as being 0 once it has spanned more than one scale change. * * * --- UPDATING P WHEN A LIQUIDATION OCCURS --- * * Please see the implementation spec in the proof document, which closely follows on from the * compounded deposit / Collateral amount gain derivations: * https://github.com/liquity/liquity/blob/master/papers/Scalable_Reward_Distribution_with_Compounding_Stakes.pdf * * * --- Gravita ISSUANCE TO STABILITY POOL DEPOSITORS --- * * An Gravita issuance event occurs at every deposit operation, and every liquidation. * * All deposits earn a share of the issued Gravita in proportion to the deposit as a share of total * deposits. * * Please see the system Readme for an overview: * https://github.com/liquity/dev/blob/main/README.md#lqty-issuance-to-stability-providers * * We use the same mathematical product-sum approach to track Gravita gains for depositors, where * 'G' is the sum corresponding to Gravita gains. * The product P (and snapshot P_t) is re-used, as the ratio P/P_t tracks a deposit's depletion due * to liquidations. * */ contract StabilityPool is ReentrancyGuardUpgradeable, UUPSUpgradeable, TrenBase, IStabilityPool { using SafeERC20 for IERC20; string public constant NAME = "StabilityPool"; // Tracker for debtToken held in the pool. Changes when users deposit/withdraw, and when TrenBox // debt is offset. uint256 internal totalDebtTokenDeposits; // totalColl.tokens and totalColl.amounts should be the same length and // always be the same length as IAdminContract(adminContract).validCollaterals(). // Anytime a new collateral is added to AdminContract, both lists are lengthened Colls internal totalColl; mapping(address depositor => uint256 amount) public deposits; /* * depositSnapshots maintains an entry for each depositor * that tracks P, S, G, scale, and epoch. * depositor's snapshot is updated only when they * deposit or withdraw from stability pool * depositSnapshots are used to allocate TREN rewards, calculate compoundedDepositAmount * and to calculate how much Collateral amount the depositor is entitled to */ mapping(address depositor => Snapshots snapshot) public depositSnapshots; /* Product 'P': Running product by which to multiply an initial deposit, in order to find the current compounded deposit, * after a series of liquidations have occurred, each of which cancel some debt tokens debt with the deposit. * * During its lifetime, a deposit's value evolves from d_t to d_t * P / P_t , where P_t * is the snapshot of P taken at the instant the deposit was made. 18-digit decimal. */ uint256 public P; uint256 public constant SCALE_FACTOR = 1e9; // Each time the scale of P shifts by SCALE_FACTOR, the scale is incremented by 1 uint128 public currentScale; // With each offset that fully empties the Pool, the epoch is incremented by 1 uint128 public currentEpoch; /* Collateral amount Gain sum 'S': During its lifetime, each deposit d_t earns an Collateral amount gain of ( d_t * [S - S_t] )/P_t, * where S_t is the depositor's snapshot of S taken at the time t when the deposit was made. * * The 'S' sums are stored in a nested mapping (epoch => scale => sum): * * - The inner mapping records the (scale => sum) * - The middle mapping records (epoch => (scale => sum)) * - The outer mapping records (collateralType => (epoch => (scale => sum))) */ mapping( address collateral => mapping(uint128 collateralType => mapping(uint128 epoch => uint256 sum)) ) public epochToScaleToSum; /* * Similarly, the sum 'G' is used to calculate TREN gains. During it's lifetime, each deposit d_t earns a TREN gain of * ( d_t * [G - G_t] )/P_t, where G_t is the depositor's snapshot of G taken at time t when the deposit was made. * * TREN reward events occur are triggered by depositor operations (new deposit, topup, withdrawal), and liquidations. * In each case, the TREN reward is issued (i.e. G is updated), before other state changes are made. */ mapping(uint128 epoch => mapping(uint128 scale => uint256 G)) public epochToScaleToG; // Error tracker for the error correction in the TREN issuance calculation uint256 public lastTRENError; // Error trackers for the error correction in the offset calculation uint256[] public lastAssetError_Offset; uint256 public lastDebtTokenLossError_Offset; // --- Initializer --- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); P = DECIMAL_PRECISION; } /** * @notice add a collateral * @dev should be called anytime a collateral is added to controller * keeps all arrays the correct length * @param _collateral address of collateral to add */ function addCollateralType(address _collateral) external onlyAdminContract { lastAssetError_Offset.push(0); totalColl.tokens.push(_collateral); totalColl.amounts.push(0); } /** * @notice get collateral balance in the SP for a given collateral type * @dev Not necessarily this contract's actual collateral balance; * just what is stored in state * @param _collateral address of the collateral to get amount of * @return amount of this specific collateral */ function getCollateral(address _collateral) external view returns (uint256) { uint256 collateralIndex = IAdminContract(adminContract).getIndex(_collateral); return totalColl.amounts[collateralIndex]; } /** * @notice getter function * @dev gets collateral from totalColl * This is not necessarily the contract's actual collateral balance; * just what is stored in state * @return tokens and amounts */ function getAllCollateral() external view returns (address[] memory, uint256[] memory) { return (totalColl.tokens, totalColl.amounts); } /** * @notice getter function * @dev gets total debtToken from deposits * @return totalDebtTokenDeposits */ function getTotalDebtTokenDeposits() external view override returns (uint256) { return totalDebtTokenDeposits; } // --- External Depositor Functions --- /** * @notice Used to provide debt tokens to the stability Pool * @dev Triggers a TREN issuance, based on time passed since the last issuance. * The TREN issuance is shared between *all* depositors * - Sends depositor's accumulated gains (TREN, collateral assets) to depositor * - Increases deposit stake, and takes new snapshots for each. * @param _amount amount of debtToken provided * @param _assets an array of collaterals to be claimed. * Skipping a collateral forfeits the available rewards (can be useful for gas optimizations) */ function provideToSP( uint256 _amount, address[] calldata _assets ) external override nonReentrant { _requireNonZeroAmount(_amount); uint256 initialDeposit = deposits[msg.sender]; _triggerTRENIssuance(); (address[] memory gainAssets, uint256[] memory gainAmounts) = getDepositorGains(msg.sender, _assets); uint256 compoundedDeposit = getCompoundedDebtTokenDeposits(msg.sender); uint256 loss = initialDeposit - compoundedDeposit; // Needed only for event log // First pay out any TREN gains _payOutTRENGains(msg.sender); // just pulls debtTokens into the pool, updates totalDeposits variable for the stability // pool and throws an event _sendToStabilityPool(msg.sender, _amount); uint256 newDeposit = compoundedDeposit + _amount; _updateDepositAndSnapshots(msg.sender, newDeposit); emit UserDepositChanged(msg.sender, newDeposit); // loss required for event log emit GainsWithdrawn(msg.sender, gainAssets, gainAmounts, loss); // send any collateral gains accrued to the depositor _sendGainsToDepositor(msg.sender, gainAssets, gainAmounts); } /** * @param _amount amount of debtToken to withdraw * @param _assets an array of collaterals to be claimed. */ function withdrawFromSP(uint256 _amount, address[] calldata _assets) external nonReentrant { (address[] memory assets, uint256[] memory amounts) = _withdrawFromSP(_amount, _assets); _sendGainsToDepositor(msg.sender, assets, amounts); } /** * @notice withdraw from the stability pool * @param _amount debtToken amount to withdraw * @param _assets an array of collaterals to be claimed. * @return assets address of assets withdrawn, amount of asset withdrawn */ function _withdrawFromSP( uint256 _amount, address[] calldata _assets ) internal returns (address[] memory assets, uint256[] memory amounts) { uint256 initialDeposit = deposits[msg.sender]; _requireUserHasDeposit(initialDeposit); _triggerTRENIssuance(); (assets, amounts) = getDepositorGains(msg.sender, _assets); uint256 compoundedDeposit = getCompoundedDebtTokenDeposits(msg.sender); uint256 debtTokensToWithdraw = TrenMath._min(_amount, compoundedDeposit); uint256 loss = initialDeposit - compoundedDeposit; // Needed only for event log // First pay out any TREN gains _payOutTRENGains(msg.sender); _sendToDepositor(msg.sender, debtTokensToWithdraw); // Update deposit uint256 newDeposit = compoundedDeposit - debtTokensToWithdraw; _updateDepositAndSnapshots(msg.sender, newDeposit); emit UserDepositChanged(msg.sender, newDeposit); emit GainsWithdrawn(msg.sender, assets, amounts, loss); // loss required for event log } // --- TREN issuance functions --- function _triggerTRENIssuance() internal { if (communityIssuance != address(0)) { uint256 TRENIssuance = ICommunityIssuance(communityIssuance).issueTREN(); _updateG(TRENIssuance); } } function _updateG(uint256 _TRENIssuance) internal { uint256 cachedTotalDebtTokenDeposits = totalDebtTokenDeposits; // cached to save an SLOAD /* * When total deposits is 0, G is not updated. In this case, the TREN issued can not be obtained by later * depositors - it is missed out on, and remains in the balanceof the CommunityIssuance contract. * */ if (cachedTotalDebtTokenDeposits == 0 || _TRENIssuance == 0) { return; } uint256 TRENPerUnitStaked = _computeTRENPerUnitStaked(_TRENIssuance, cachedTotalDebtTokenDeposits); uint256 marginalTRENGain = TRENPerUnitStaked * P; uint256 newEpochToScaleToG = epochToScaleToG[currentEpoch][currentScale]; newEpochToScaleToG += marginalTRENGain; epochToScaleToG[currentEpoch][currentScale] = newEpochToScaleToG; emit GainsUpdated(newEpochToScaleToG, currentEpoch, currentScale); } function _computeTRENPerUnitStaked( uint256 _TRENIssuance, uint256 _totalDeposits ) internal returns (uint256) { /* * Calculate the TREN-per-unit staked. Division uses a "feedback" error correction, to keep the * cumulative error low in the running total G: * * 1) Form a numerator which compensates for the floor division error that occurred the last time this * function was called. * 2) Calculate "per-unit-staked" ratio. * 3) Multiply the ratio back by its denominator, to reveal the current floor division error. * 4) Store this error for use in the next correction when this function is called. * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended. */ uint256 TRENNumerator = (_TRENIssuance * DECIMAL_PRECISION) + lastTRENError; uint256 TRENPerUnitStaked = TRENNumerator / _totalDeposits; lastTRENError = TRENNumerator - (TRENPerUnitStaked * _totalDeposits); return TRENPerUnitStaked; } // --- Liquidation functions --- /** * @notice sets the offset for liquidation * @dev Cancels out the specified debt against the debtTokens contained in the Stability Pool * (as far as possible) * and transfers the TrenBox's collateral from ActivePool to StabilityPool. * Only called by liquidation functions in the TrenBoxManager. * @param _debtToOffset how much debt to offset * @param _asset token address * @param _amountAdded token amount as uint256 */ function offset( uint256 _debtToOffset, address _asset, uint256 _amountAdded ) external onlyTrenBoxManager { uint256 cachedTotalDebtTokenDeposits = totalDebtTokenDeposits; // cached to save an SLOAD if (cachedTotalDebtTokenDeposits == 0 || _debtToOffset == 0) { return; } _triggerTRENIssuance(); (uint256 collGainPerUnitStaked, uint256 debtLossPerUnitStaked) = _computeRewardsPerUnitStaked( _asset, _amountAdded, _debtToOffset, cachedTotalDebtTokenDeposits ); _updateRewardSumAndProduct(_asset, collGainPerUnitStaked, debtLossPerUnitStaked); // updates // S and P _moveOffsetCollAndDebt(_asset, _amountAdded, _debtToOffset); } // --- Offset helper functions --- /** * @notice Compute the debtToken and Collateral amount rewards. Uses a "feedback" error * correction, to keep * the cumulative error in the P and S state variables low: * * @dev 1) Form numerators which compensate for the floor division errors that occurred the last * time this * function was called. * 2) Calculate "per-unit-staked" ratios. * 3) Multiply each ratio back by its denominator, to reveal the current floor division error. * 4) Store these errors for use in the next correction when this function is called. * 5) Note: static analysis tools complain about this "division before multiplication", however, * it is intended. * @param _asset Address of token * @param _amountAdded amount as uint256 * @param _debtToOffset amount of debt to offset * @param _totalDeposits How much user has deposited */ function _computeRewardsPerUnitStaked( address _asset, uint256 _amountAdded, uint256 _debtToOffset, uint256 _totalDeposits ) internal returns (uint256 collGainPerUnitStaked, uint256 debtLossPerUnitStaked) { uint256 assetIndex = IAdminContract(adminContract).getIndex(_asset); uint256 collateralNumerator = (_amountAdded * DECIMAL_PRECISION) + lastAssetError_Offset[assetIndex]; if (_debtToOffset > _totalDeposits) { revert StabilityPool__DebtLargerThanTotalDeposits(); } if (_debtToOffset == _totalDeposits) { debtLossPerUnitStaked = DECIMAL_PRECISION; // When the Pool depletes to 0, so does each // deposit lastDebtTokenLossError_Offset = 0; } else { uint256 lossNumerator = (_debtToOffset * DECIMAL_PRECISION) - lastDebtTokenLossError_Offset; /* * Add 1 to make error in quotient positive. We want "slightly too much" loss, * which ensures the error in any given compoundedDeposit favors the Stability Pool. */ debtLossPerUnitStaked = (lossNumerator / _totalDeposits) + 1; lastDebtTokenLossError_Offset = (debtLossPerUnitStaked * _totalDeposits) - lossNumerator; } collGainPerUnitStaked = collateralNumerator / _totalDeposits; lastAssetError_Offset[assetIndex] = collateralNumerator - (collGainPerUnitStaked * _totalDeposits); } function _updateRewardSumAndProduct( address _asset, uint256 _collGainPerUnitStaked, uint256 _debtLossPerUnitStaked ) internal { if (_debtLossPerUnitStaked > DECIMAL_PRECISION) { revert StabilityPool__DebtLossBelowOne(_debtLossPerUnitStaked); } uint256 currentP = P; uint256 newP; /* * The newProductFactor is the factor by which to change all deposits, due to the depletion of Stability Pool debt tokens in the liquidation. * We make the product factor 0 if there was a pool-emptying. Otherwise, it is (1 - _debtLossPerUnitStaked) */ uint256 newProductFactor = DECIMAL_PRECISION - _debtLossPerUnitStaked; uint128 currentScaleCached = currentScale; uint128 currentEpochCached = currentEpoch; uint256 currentS = epochToScaleToSum[_asset][currentEpochCached][currentScaleCached]; /* * Calculate the new S first, before we update P. * The asset gain for any given depositor from a liquidation depends on the value of their deposit * (and the value of totalDeposits) prior to the Stability being depleted by the debt in the liquidation. * * Since S corresponds to asset gain, and P to deposit loss, we update S first. */ uint256 marginalAssetGain = _collGainPerUnitStaked * currentP; uint256 newS = currentS + marginalAssetGain; epochToScaleToSum[_asset][currentEpochCached][currentScaleCached] = newS; emit SumUpdated(_asset, newS, currentEpochCached, currentScaleCached); // If the Stability Pool was emptied, increment the epoch, and reset the scale and product P if (newProductFactor == 0) { currentEpochCached += 1; currentEpoch = currentEpochCached; emit EpochUpdated(currentEpochCached); currentScale = 0; emit ScaleUpdated(0); newP = DECIMAL_PRECISION; // If multiplying P by a non-zero product factor would reduce P below the scale // boundary, increment the scale } else { uint256 mulCached = currentP * newProductFactor; uint256 mulDivCached = mulCached / DECIMAL_PRECISION; if (mulDivCached < SCALE_FACTOR) { newP = (mulCached * SCALE_FACTOR) / DECIMAL_PRECISION; currentScaleCached += 1; currentScale = currentScaleCached; emit ScaleUpdated(currentScaleCached); } else { newP = mulDivCached; } } if (newP == 0) { revert StabilityPool__ProductZero(); } P = newP; emit ProductUpdated(newP); } /** * @notice Internal function to move offset collateral and debt between pools. * @dev Cancel the liquidated debtToken debt with the debtTokens in the stability pool, * Burn the debt that was successfully offset. Collateral is moved from * the ActivePool to this contract. * @param _asset collateral address * @param _amount amount as uint256 * @param _debtToOffset uint256 */ function _moveOffsetCollAndDebt( address _asset, uint256 _amount, uint256 _debtToOffset ) internal { IActivePool(activePool).decreaseDebt(_asset, _debtToOffset); _decreaseDebtTokens(_debtToOffset); IDebtToken(debtToken).burn(address(this), _debtToOffset); IActivePool(activePool).sendAsset(_asset, address(this), _amount); } function _decreaseDebtTokens(uint256 _amount) internal { uint256 newTotalDeposits = totalDebtTokenDeposits - _amount; totalDebtTokenDeposits = newTotalDeposits; emit StabilityPoolDebtTokenBalanceUpdated(newTotalDeposits); } // --- Reward calculator functions for depositor --- /** * @notice Calculates the gains earned by the deposit since its last snapshots were taken for * selected assets. * @dev Given by the formula: E = d0 * (S - S(0))/P(0) * where S(0) and P(0) are the depositor's snapshots of the sum S and product P, respectively. * d0 is the last recorded deposit value. * @param _depositor address of depositor in question * @param _assets array of assets to check gains for * @return assets, amounts */ function getDepositorGains( address _depositor, address[] memory _assets ) public view returns (address[] memory, uint256[] memory) { uint256 initialDeposit = deposits[_depositor]; if (initialDeposit == 0) { address[] memory emptyAddress = new address[](0); uint256[] memory emptyUint = new uint256[](0); return (emptyAddress, emptyUint); } Snapshots storage snapshots = depositSnapshots[_depositor]; uint256[] memory amountsFromNewGains = _calculateNewGains(initialDeposit, snapshots, _assets); return (_assets, amountsFromNewGains); } /** * @notice get gains on each possible asset by looping through * @dev assets with _getGainFromSnapshots function * @param initialDeposit Amount of initial deposit * @param snapshots struct snapshots * @param _assets ascending ordered array of assets to calculate and claim gains */ function _calculateNewGains( uint256 initialDeposit, Snapshots storage snapshots, address[] memory _assets ) internal view returns (uint256[] memory amounts) { uint256 assetsLen = _assets.length; // asset list must be on ascending order - used to avoid any repeated elements unchecked { for (uint256 i = 1; i < assetsLen; i++) { if (_assets[i] <= _assets[i - 1]) { revert StabilityPool__ArrayNotInAscendingOrder(); } } } amounts = new uint256[](assetsLen); for (uint256 i = 0; i < assetsLen;) { amounts[i] = _getGainFromSnapshots(initialDeposit, snapshots, _assets[i]); unchecked { i++; } } } /** * @notice gets the gain in S for a given asset * @dev for a user who deposited initialDeposit * @param initialDeposit Amount of initialDeposit * @param snapshots struct snapshots * @param asset asset to gain snapshot * @return uint256 the gain */ function _getGainFromSnapshots( uint256 initialDeposit, Snapshots storage snapshots, address asset ) internal view returns (uint256) { /* * Grab the sum 'S' from the epoch at which the stake was made. The Collateral amount gain may span up to one scale change. * If it does, the second portion of the Collateral amount gain is scaled by 1e9. * If the gain spans no scale change, the second portion will be 0. */ uint256 S_Snapshot = snapshots.S[asset]; uint256 P_Snapshot = snapshots.P; mapping(uint128 => uint256) storage scaleToSum = epochToScaleToSum[asset][snapshots.epoch]; uint256 firstPortion = scaleToSum[snapshots.scale] - S_Snapshot; uint256 secondPortion = scaleToSum[snapshots.scale + 1] / SCALE_FACTOR; uint256 assetGain = (initialDeposit * (firstPortion + secondPortion)) / P_Snapshot / DECIMAL_PRECISION; return assetGain; } /* * Calculate the TREN gain earned by a deposit since its last snapshots were taken. * Given by the formula: TREN = d0 * (G - G(0))/P(0) * where G(0) and P(0) are the depositor's snapshots of the sum G and product P, respectively. * d0 is the last recorded deposit value. */ function getDepositorTRENGain(address _depositor) public view override returns (uint256) { uint256 initialDeposit = deposits[_depositor]; if (initialDeposit == 0) { return 0; } Snapshots storage snapshots = depositSnapshots[_depositor]; return _getTRENGainFromSnapshots(initialDeposit, snapshots); } function _getTRENGainFromSnapshots( uint256 initialStake, Snapshots storage snapshots ) internal view returns (uint256) { /* * Grab the sum 'G' from the epoch at which the stake was made. The TREN gain may span up to one scale change. * If it does, the second portion of the TREN gain is scaled by 1e9. * If the gain spans no scale change, the second portion will be 0. */ uint128 epochSnapshot = snapshots.epoch; uint128 scaleSnapshot = snapshots.scale; uint256 G_Snapshot = snapshots.G; uint256 P_Snapshot = snapshots.P; uint256 firstPortion = epochToScaleToG[epochSnapshot][scaleSnapshot] - G_Snapshot; uint256 secondPortion = epochToScaleToG[epochSnapshot][scaleSnapshot + 1] / SCALE_FACTOR; uint256 TRENGain = (initialStake * (firstPortion + secondPortion)) / P_Snapshot / DECIMAL_PRECISION; return TRENGain; } // --- Compounded deposit and compounded System stake --- /* * Return the user's compounded deposit. Given by the formula: d = d0 * P/P(0) * where P(0) is the depositor's snapshot of the product P, taken when they last updated their deposit. */ function getCompoundedDebtTokenDeposits(address _depositor) public view override returns (uint256) { uint256 initialDeposit = deposits[_depositor]; if (initialDeposit == 0) { return 0; } return _getCompoundedStakeFromSnapshots(initialDeposit, depositSnapshots[_depositor]); } // Internal function, used to calculate compounded deposits and compounded stakes. function _getCompoundedStakeFromSnapshots( uint256 initialStake, Snapshots storage snapshots ) internal view returns (uint256) { uint256 snapshot_P = snapshots.P; uint128 scaleSnapshot = snapshots.scale; uint128 epochSnapshot = snapshots.epoch; // If stake was made before a pool-emptying event, then it has been fully cancelled with // debt -- so, return 0 if (epochSnapshot < currentEpoch) { return 0; } uint256 compoundedStake; uint128 scaleDiff = currentScale - scaleSnapshot; /* Compute the compounded stake. If a scale change in P was made during the stake's lifetime, * account for it. If more than one scale change was made, then the stake has decreased by a factor of * at least 1e-9 -- so return 0. */ if (scaleDiff == 0) { compoundedStake = (initialStake * P) / snapshot_P; } else if (scaleDiff == 1) { compoundedStake = (initialStake * P) / snapshot_P / SCALE_FACTOR; } else { compoundedStake = 0; } /* * If compounded deposit is less than a billionth of the initial deposit, return 0. * * NOTE: originally, this line was in place to stop rounding errors making the deposit too large. However, the error * corrections should ensure the error in P "favors the Pool", i.e. any given compounded deposit should slightly less * than it's theoretical value. * * Thus it's unclear whether this line is still really needed. */ if (compoundedStake < initialStake / 1e9) { return 0; } return compoundedStake; } // --- Sender functions for debtToken deposits // Transfer the tokens from the user to the Stability Pool's address, and update its recorded // deposits function _sendToStabilityPool(address _address, uint256 _amount) internal { IDebtToken(debtToken).sendToPool(_address, address(this), _amount); uint256 newTotalDeposits = totalDebtTokenDeposits + _amount; totalDebtTokenDeposits = newTotalDeposits; emit StabilityPoolDebtTokenBalanceUpdated(newTotalDeposits); } /** * @notice transfer collateral gains to the depositor * @dev this function also unwraps wrapped assets * before sending to depositor * @param _to address * @param assets array of address * @param amounts array of uint256. Includes pending collaterals since that was added in * previous steps */ function _sendGainsToDepositor( address _to, address[] memory assets, uint256[] memory amounts ) internal { uint256 assetsLen = assets.length; if (assetsLen != amounts.length) { revert StabilityPool__AssetsAndAmountsLengthMismatch(); } for (uint256 i = 0; i < assetsLen;) { uint256 amount = amounts[i]; if (amount == 0) { unchecked { i++; } continue; } address asset = assets[i]; // Assumes we're internally working only with the wrapped version of ERC20 tokens IERC20(asset).safeTransfer(_to, amount); unchecked { i++; } } totalColl.amounts = _leftSubColls(totalColl, assets, amounts); } // Send debt tokens to user and decrease deposits in Pool function _sendToDepositor(address _depositor, uint256 debtTokenWithdrawal) internal { if (debtTokenWithdrawal == 0) { return; } IDebtToken(debtToken).returnFromPool(address(this), _depositor, debtTokenWithdrawal); _decreaseDebtTokens(debtTokenWithdrawal); } // --- Stability Pool Deposit Functionality --- /** * @notice updates deposit and snapshots internally * @dev if _newValue is zero, delete snapshot for given _depositor and emit event * otherwise, add an entry or update existing entry for _depositor in the depositSnapshots * with current values for P, S, G, scale and epoch and then emit event. * @param _depositor address * @param _newValue uint256 */ function _updateDepositAndSnapshots(address _depositor, uint256 _newValue) internal { deposits[_depositor] = _newValue; address[] memory colls = IAdminContract(adminContract).getValidCollateral(); uint256 collsLen = colls.length; Snapshots storage depositorSnapshots = depositSnapshots[_depositor]; if (_newValue == 0) { for (uint256 i = 0; i < collsLen;) { depositSnapshots[_depositor].S[colls[i]] = 0; unchecked { i++; } } depositorSnapshots.P = 0; depositorSnapshots.G = 0; depositorSnapshots.epoch = 0; depositorSnapshots.scale = 0; emit DepositSnapshotUpdated(_depositor, 0, 0); return; } uint128 currentScaleCached = currentScale; uint128 currentEpochCached = currentEpoch; uint256 currentP = P; for (uint256 i = 0; i < collsLen;) { address asset = colls[i]; uint256 currentS = epochToScaleToSum[asset][currentEpochCached][currentScaleCached]; depositSnapshots[_depositor].S[asset] = currentS; unchecked { i++; } } uint256 currentG = epochToScaleToG[currentEpochCached][currentScaleCached]; depositorSnapshots.P = currentP; depositorSnapshots.G = currentG; depositorSnapshots.scale = currentScaleCached; depositorSnapshots.epoch = currentEpochCached; emit DepositSnapshotUpdated(_depositor, currentP, currentG); } function S(address _depositor, address _asset) external view returns (uint256) { return depositSnapshots[_depositor].S[_asset]; } function _payOutTRENGains(address _depositor) internal { if (address(communityIssuance) != address(0)) { uint256 depositorTRENGain = getDepositorTRENGain(_depositor); ICommunityIssuance(communityIssuance).sendTREN(_depositor, depositorTRENGain); emit TRENPaidToDepositor(_depositor, depositorTRENGain); } } function _leftSubColls( Colls memory _coll1, address[] memory _tokens, uint256[] memory _amounts ) internal pure returns (uint256[] memory) { uint256 coll1Len = _coll1.amounts.length; uint256 tokensLen = _tokens.length; for (uint256 i = 0; i < coll1Len;) { for (uint256 j = 0; j < tokensLen;) { if (_coll1.tokens[i] == _tokens[j]) { _coll1.amounts[i] -= _amounts[j]; } unchecked { j++; } } unchecked { i++; } } return _coll1.amounts; } function _requireUserHasDeposit(uint256 _initialDeposit) internal pure { if (_initialDeposit == 0) { revert StabilityPool__UserHasNoDeposit(); } } function _requireNonZeroAmount(uint256 _amount) internal pure { if (_amount == 0) { revert StabilityPool__AmountMustBeNonZero(); } } // --- Modifiers --- modifier onlyAdminContract() { if (msg.sender != adminContract) { revert StabilityPool__AdminContractOnly(msg.sender, adminContract); } _; } modifier onlyActivePool() { if (msg.sender != activePool) { revert StabilityPool__ActivePoolOnly(msg.sender, activePool); } _; } modifier onlyTrenBoxManager() { if (msg.sender != trenBoxManager) { revert StabilityPool__TrenBoxManagerOnly(msg.sender, trenBoxManager); } _; } // --- Fallback function --- function receivedERC20(address _asset, uint256 _amount) external override onlyActivePool { uint256 collateralIndex = IAdminContract(adminContract).getIndex(_asset); uint256 newAssetBalance = totalColl.amounts[collateralIndex] + _amount; totalColl.amounts[collateralIndex] = newAssetBalance; emit StabilityPoolAssetBalanceUpdated(_asset, newAssetBalance); } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../Interfaces/IFlashLoan.sol"; import "../Interfaces/IFlashLoanReceiver.sol"; contract FlashLoanTester is IFlashLoanReceiver { address public flashLoan; function setFlashLoanAddress(address _flashLoan) external { flashLoan = _flashLoan; } function executeFlashLoan(uint256 _amount) external { IFlashLoan(flashLoan).flashLoan(_amount); } function executeOperation(uint256 _amount, uint256 _fee, address _tokenAddress) external { // Here you can do anything what you want IERC20(_tokenAddress).transfer(msg.sender, _amount + _fee); } function withdrawTokens(address _tokenAddress, address _receiver) external { uint256 _amount = IERC20(_tokenAddress).balanceOf(address(this)); IERC20(_tokenAddress).transfer(_receiver, _amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract MockAggregator is AggregatorV3Interface { uint8 private decimalsVal = 8; int256 private answer = 190_000_000_000; int256 private prevAnswer = 190_000_000_000; uint256 private startedAt; uint256 private updatedAt; uint80 private latestRoundId = 2; uint80 private prevRoundId = 1; bool priceIsAlwaysUpToDate = true; // --- Functions --- function setDecimals(uint8 _decimals) external { decimalsVal = _decimals; } function setPrice(int256 _price) external { answer = _price; } function setPrevPrice(int256 _prevPrice) external { prevAnswer = _prevPrice; } function setStartedAt(uint256 _startedAt) external { startedAt = _startedAt; } function setUpdatedAt(uint256 _updatedAt) external { updatedAt = _updatedAt; } function setLatestRoundId(uint80 _latestRoundId) external { latestRoundId = _latestRoundId; } function setPrevRoundId(uint80 _prevRoundId) external { prevRoundId = _prevRoundId; } function setPriceIsAlwaysUpToDate(bool _priceIsAlwaysUpToDate) external { priceIsAlwaysUpToDate = _priceIsAlwaysUpToDate; } // --- Getters that adhere to the AggregatorV3 interface --- function decimals() external view override returns (uint8) { return decimalsVal; } function latestRoundData() external view override returns (uint80, int256, uint256, uint256, uint80) { uint256 timestamp = priceIsAlwaysUpToDate ? block.timestamp - 2 minutes : updatedAt; return (latestRoundId, answer, startedAt, timestamp, 0); } function getRoundData(uint80) external view override returns (uint80, int256, uint256, uint256, uint80) { uint256 timestamp = priceIsAlwaysUpToDate ? block.timestamp - 5 minutes : updatedAt; return (prevRoundId, prevAnswer, startedAt, timestamp, 0); } function description() external pure override returns (string memory) { return ""; } function version() external pure override returns (uint256) { return 1; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { API3ProxyInterface } from "../Pricing/API3ProxyInterface.sol"; contract MockApi3Proxy is API3ProxyInterface { int224 public value = 1_012_695_777_067_725_000; function setValue(int224 _newValue) external { value = _newValue; } function read() external view override returns (int224, uint32) { return (value, uint32(block.timestamp)); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // This contract is for temporary replacement main BorrowerOpearations in testing // flashLoanForRepay() in FlashLoan.sol contract MockBorrowerOperations { function closeTrenBox(address _asset) external { IERC20(_asset).transfer(msg.sender, IERC20(_asset).balanceOf(address(this))); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { IUniswapRouterV3 } from "../Interfaces/IUniswapRouterV3.sol"; contract MockUniswapRouterV3 is IUniswapRouterV3 { using BytesLib for bytes; uint256 private constant FEE_DENOMINATOR = 1_000_000; uint256 private constant ADDR_SIZE = 20; uint256 private constant FEE_SIZE = 3; uint256 private constant NEXT_OFFSET = ADDR_SIZE + FEE_SIZE; uint256 ratioAssetToStable = 3000; uint256 ratioStableToDebt = 1; struct SwapCallbackData { bytes path; address payer; } function setRatio(uint256 _ratioAssetToStable, uint256 _ratioStableToDebt) external { ratioAssetToStable = _ratioAssetToStable; ratioStableToDebt = _ratioStableToDebt; } function exactOutput(ExactOutputParams memory params) external returns (uint256 amountIn) { (address debtToken, address stableCoin, address assetToken, uint24 fee1, uint24 fee2) = decodePath(params.path); uint256 stableCoinsNeeded = params.amountOut * ratioStableToDebt; uint256 fee_1 = (stableCoinsNeeded * fee1) / FEE_DENOMINATOR; uint256 assetTokensNeeded = (stableCoinsNeeded + fee_1) / ratioAssetToStable; uint256 fee_2 = (assetTokensNeeded * fee2) / FEE_DENOMINATOR; uint256 assetTokensNeededPlusFee = assetTokensNeeded + fee_2; IERC20(assetToken).transferFrom(params.recipient, address(this), assetTokensNeededPlusFee); IERC20(debtToken).transfer(params.recipient, params.amountOut); return assetTokensNeededPlusFee; } function decodePath(bytes memory path) internal pure returns (address tokenA, address tokenB, address tokenC, uint24 fee1, uint24 fee2) { tokenA = path.toAddress(0); fee1 = path.toUint24(ADDR_SIZE); tokenB = path.toAddress(NEXT_OFFSET); fee2 = path.toUint24(NEXT_OFFSET + ADDR_SIZE); tokenC = path.toAddress(NEXT_OFFSET + ADDR_SIZE + FEE_SIZE); } } library BytesLib { function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { require(_start + 20 >= _start, "toAddress_overflow"); require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); address tempAddress; assembly { tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) } return tempAddress; } function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { require(_start + 3 >= _start, "toUint24_overflow"); require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); uint24 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x3), _start)) } return tempUint; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import "../Interfaces/IPriceFeed.sol"; /* * PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state * variable. The contract does not connect to a live Chainlink price feed. */ contract PriceFeedTestnet is IPriceFeed { string public constant NAME = "PriceFeedTestnet"; mapping(address => uint256) public prices; function getPrice(address _asset) external view returns (uint256) { return prices[_asset]; } function setPrice(address _asset, uint256 _price) external { prices[_asset] = _price; } function setOracle( address _token, address _oracle, ProviderType _type, uint256 _timeoutMinutes, bool _isEthIndexed, bool _isFallback ) external override { } function fetchPrice(address _asset) external view override returns (uint256) { return this.getPrice(_asset); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { TrenMath } from "../Dependencies/TrenMath.sol"; contract TrenMathTester { function min(uint256 _a, uint256 _b) external pure returns (uint256) { return TrenMath._min(_a, _b); } function max(uint256 _a, uint256 _b) external pure returns (uint256) { return TrenMath._max(_a, _b); } /** * @dev Multiply two decimal numbers and use normal rounding rules: * -round product up if 19'th mantissa digit >= 5 * -round product down if 19'th mantissa digit < 5 * * Used only inside the exponentiation, _decPow(). */ function decMul(uint256 x, uint256 y) external pure returns (uint256) { return TrenMath.decMul(x, y); } /** * @dev Exponentiation function for 18-digit decimal base, and integer exponent n. * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity. */ function decPow(uint256 _base, uint256 _minutes) external pure returns (uint256) { return TrenMath._decPow(_base, _minutes); } function getAbsoluteDifference(uint256 _a, uint256 _b) external pure returns (uint256) { return TrenMath._getAbsoluteDifference(_a, _b); } function computeNominalCR(uint256 _coll, uint256 _debt) external pure returns (uint256) { return TrenMath._computeNominalCR(_coll, _debt); } function computeCR( uint256 _coll, uint256 _debt, uint256 _price ) external pure returns (uint256) { return TrenMath._computeCR(_coll, _debt, _price); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; error Timelock__DelayMustExceedMininumDelay(); error Timelock__DelayMustNotExceedMaximumDelay(); error Timelock__OnlyTimelock(); error Timelock__OnlyPendingAdmin(); error Timelock__OnlyAdmin(); error Timelock__ETAMustSatisfyDelay(); error Timelock__TxNoQueued(); error Timelock__TxAlreadyQueued(); error Timelock__TxStillLocked(); error Timelock__TxExpired(); error Timelock__TxReverted(); error Timelock__AdminZeroAddress(); error Timelock__TargetZeroAddress(); contract Timelock { // ------------------------------------------- State ------------------------------------------ string public constant NAME = "Timelock"; uint256 public constant GRACE_PERIOD = 14 days; uint256 public constant MINIMUM_DELAY = 2 days; uint256 public constant MAXIMUM_DELAY = 15 days; address public admin; address public pendingAdmin; uint256 public delay; mapping(bytes32 txHash => bool isQueued) public queuedTransactions; event NewAdmin(address indexed newAdmin); event NewPendingAdmin(address indexed newPendingAdmin); event NewDelay(uint256 indexed newDelay); event CancelTransaction( bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta ); event ExecuteTransaction( bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta ); event QueueTransaction( bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta ); // ------------------------------------------ Modifiers --------------------------------------- modifier isValidDelay(uint256 _delay) virtual { if (_delay < MINIMUM_DELAY) { revert Timelock__DelayMustExceedMininumDelay(); } if (_delay > MAXIMUM_DELAY) { revert Timelock__DelayMustNotExceedMaximumDelay(); } _; } modifier OnlyAdmin() { if (msg.sender != admin) { revert Timelock__OnlyAdmin(); } _; } // ------------------------------------------ Constructor ------------------------------------- constructor(uint256 _delay, address _adminAddress) isValidDelay(_delay) { if (_adminAddress == address(0)) { revert Timelock__AdminZeroAddress(); } admin = _adminAddress; delay = _delay; } // ------------------------------------------ External Functions ------------------------------ function setDelay(uint256 _delay) external isValidDelay(_delay) { if (msg.sender != address(this)) { revert Timelock__OnlyTimelock(); } delay = _delay; emit NewDelay(_delay); } function acceptAdmin() external { if (msg.sender != pendingAdmin) { revert Timelock__OnlyPendingAdmin(); } admin = msg.sender; pendingAdmin = address(0); emit NewAdmin(msg.sender); } function setPendingAdmin(address _pendingAdmin) external { if (msg.sender != address(this)) { revert Timelock__OnlyTimelock(); } if (_pendingAdmin == address(0)) { revert Timelock__AdminZeroAddress(); } pendingAdmin = _pendingAdmin; emit NewPendingAdmin(_pendingAdmin); } function queueTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) external OnlyAdmin returns (bytes32) { if (eta < block.timestamp + delay || eta > block.timestamp + delay + GRACE_PERIOD) { revert Timelock__ETAMustSatisfyDelay(); } bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); if (queuedTransactions[txHash]) { revert Timelock__TxAlreadyQueued(); } queuedTransactions[txHash] = true; emit QueueTransaction(txHash, target, value, signature, data, eta); return txHash; } function cancelTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) external OnlyAdmin { bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); if (!queuedTransactions[txHash]) { revert Timelock__TxNoQueued(); } queuedTransactions[txHash] = false; emit CancelTransaction(txHash, target, value, signature, data, eta); } function executeTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) external payable OnlyAdmin returns (bytes memory) { if (target == address(0)) { revert Timelock__TargetZeroAddress(); } bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); if (!queuedTransactions[txHash]) { revert Timelock__TxNoQueued(); } if (block.timestamp < eta) { revert Timelock__TxStillLocked(); } if (block.timestamp > eta + GRACE_PERIOD) { revert Timelock__TxExpired(); } queuedTransactions[txHash] = false; bytes memory callData; if (bytes(signature).length == 0) { callData = data; } else { callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); } (bool success, bytes memory returnData) = target.call{ value: value }(callData); if (!success) { revert Timelock__TxReverted(); } emit ExecuteTransaction(txHash, target, value, signature, data, eta); return returnData; } // ------------------------------------------- Receive function ------------------------------- receive() external payable { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ICommunityIssuance } from "../Interfaces/ICommunityIssuance.sol"; import { IStabilityPool } from "../Interfaces/IStabilityPool.sol"; contract CommunityIssuance is ICommunityIssuance, OwnableUpgradeable { using SafeERC20 for IERC20; string public constant NAME = "CommunityIssuance"; uint256 public constant DISTRIBUTION_DURATION = 7 days / 60; uint256 public constant SECONDS_IN_ONE_MINUTE = 60; uint256 public totalTRENIssued; uint256 public lastUpdateTime; uint256 public TRENSupplyCap; uint256 public trenDistribution; IERC20 public trenToken; IStabilityPool public stabilityPool; address public adminContract; bool public isSetupInitialized; modifier isController() { require(msg.sender == owner() || msg.sender == adminContract, "Invalid Permission"); _; } modifier onlyStabilityPool() { require(address(stabilityPool) == msg.sender, "CommunityIssuance: caller is not SP"); _; } // --- Initializer --- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); } // --- Functions --- function setAddresses( address _trenToken, address _stabilityPool, address _adminContract ) external onlyOwner { if (isSetupInitialized) revert CommunityIssuance__SetupAlreadyInitialized(); if ( _trenToken == address(0) || _stabilityPool == address(0) || _adminContract == address(0) ) revert CommunityIssuance__InvalidAddresses(); adminContract = _adminContract; trenToken = IERC20(_trenToken); stabilityPool = IStabilityPool(_stabilityPool); isSetupInitialized = true; } function setAdminContract(address _adminContract) external onlyOwner { if (_adminContract == address(0)) revert CommunityIssuance__InvalidAdminContractAddress(); adminContract = _adminContract; } function addFundToStabilityPool(uint256 _assignedSupply) external override isController { _addFundToStabilityPoolFrom(_assignedSupply, msg.sender); } function removeFundFromStabilityPool(uint256 _fundToRemove) external onlyOwner { uint256 newCap = TRENSupplyCap - _fundToRemove; require( totalTRENIssued <= newCap, "CommunityIssuance: Stability Pool doesn't have enough supply." ); TRENSupplyCap -= _fundToRemove; trenToken.safeTransfer(msg.sender, _fundToRemove); } function addFundToStabilityPoolFrom( uint256 _assignedSupply, address _spender ) external override isController { _addFundToStabilityPoolFrom(_assignedSupply, _spender); } function _addFundToStabilityPoolFrom(uint256 _assignedSupply, address _spender) internal { if (lastUpdateTime == 0) { lastUpdateTime = block.timestamp; } TRENSupplyCap += _assignedSupply; trenToken.safeTransferFrom(_spender, address(this), _assignedSupply); } function issueTREN() public override onlyStabilityPool returns (uint256) { uint256 maxPoolSupply = TRENSupplyCap; if (totalTRENIssued >= maxPoolSupply) return 0; uint256 issuance = _getLastUpdateTokenDistribution(); uint256 totalIssuance = issuance + totalTRENIssued; if (totalIssuance > maxPoolSupply) { issuance = maxPoolSupply - totalTRENIssued; totalIssuance = maxPoolSupply; } lastUpdateTime = block.timestamp; totalTRENIssued = totalIssuance; emit TotalTRENIssuedUpdated(totalIssuance); return issuance; } function _getLastUpdateTokenDistribution() internal view returns (uint256) { require(lastUpdateTime != 0, "Stability pool hasn't been assigned"); uint256 timePassed = (block.timestamp - lastUpdateTime) / SECONDS_IN_ONE_MINUTE; uint256 totalDistribuedSinceBeginning = trenDistribution * timePassed; return totalDistribuedSinceBeginning; } function sendTREN(address _account, uint256 _TRENamount) external override onlyStabilityPool { uint256 balanceTREN = trenToken.balanceOf(address(this)); uint256 safeAmount = balanceTREN >= _TRENamount ? _TRENamount : balanceTREN; if (safeAmount == 0) { return; } IERC20(address(trenToken)).safeTransfer(_account, safeAmount); } function setWeeklyTrenDistribution(uint256 _weeklyReward) external isController { trenDistribution = _weeklyReward / DISTRIBUTION_DURATION; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; /* This contract is reserved for Linear Vesting to the Team members and the Advisors team. */ contract LockedTREN is Ownable, Initializable { using SafeERC20 for IERC20; struct Rule { uint256 createdDate; uint256 totalSupply; uint256 startVestingDate; uint256 endVestingDate; uint256 claimed; } string public constant NAME = "LockedTREN"; uint256 public constant SIX_MONTHS = 26 weeks; uint256 public constant TWO_YEARS = 730 days; IERC20 private trenToken; uint256 private assignedTRENTokens; mapping(address => Rule) public entitiesVesting; modifier entityRuleExists(address _entity) { require(entitiesVesting[_entity].createdDate != 0, "Entity doesn't have a Vesting Rule"); _; } constructor(address initialOwner) Ownable(initialOwner) { } function setAddresses(address _trenAddress) public initializer onlyOwner { trenToken = IERC20(_trenAddress); } function addEntityVesting(address _entity, uint256 _totalSupply) public onlyOwner { require(address(0) != _entity, "Invalid Address"); require(entitiesVesting[_entity].createdDate == 0, "Entity already has a Vesting Rule"); assignedTRENTokens += _totalSupply; entitiesVesting[_entity] = Rule( block.timestamp, _totalSupply, block.timestamp + SIX_MONTHS, block.timestamp + TWO_YEARS, 0 ); trenToken.safeTransferFrom(msg.sender, address(this), _totalSupply); } function lowerEntityVesting( address _entity, uint256 newTotalSupply ) public onlyOwner entityRuleExists(_entity) { sendTRENTokenToEntity(_entity); Rule storage vestingRule = entitiesVesting[_entity]; require( newTotalSupply > vestingRule.claimed, "Total Supply goes lower or equal than the claimed total." ); vestingRule.totalSupply = newTotalSupply; } function removeEntityVesting(address _entity) public onlyOwner entityRuleExists(_entity) { sendTRENTokenToEntity(_entity); Rule memory vestingRule = entitiesVesting[_entity]; assignedTRENTokens = assignedTRENTokens - (vestingRule.totalSupply - vestingRule.claimed); delete entitiesVesting[_entity]; } function claimTRENToken() public entityRuleExists(msg.sender) { sendTRENTokenToEntity(msg.sender); } function sendTRENTokenToEntity(address _entity) private { uint256 unclaimedAmount = getClaimableTREN(_entity); if (unclaimedAmount == 0) return; Rule storage entityRule = entitiesVesting[_entity]; entityRule.claimed += unclaimedAmount; assignedTRENTokens = assignedTRENTokens - unclaimedAmount; trenToken.safeTransfer(_entity, unclaimedAmount); } function transferUnassignedTREN() external onlyOwner { uint256 unassignedTokens = getUnassignTRENTokensAmount(); if (unassignedTokens == 0) return; trenToken.safeTransfer(msg.sender, unassignedTokens); } function getClaimableTREN(address _entity) public view returns (uint256 claimable) { Rule memory entityRule = entitiesVesting[_entity]; claimable = 0; if (entityRule.startVestingDate > block.timestamp) return claimable; if (block.timestamp >= entityRule.endVestingDate) { claimable = entityRule.totalSupply - entityRule.claimed; } else { claimable = ( (entityRule.totalSupply / TWO_YEARS) * (block.timestamp - entityRule.createdDate) ) - entityRule.claimed; } return claimable; } function getUnassignTRENTokensAmount() public view returns (uint256) { return trenToken.balanceOf(address(this)) - assignedTRENTokens; } function isEntityExits(address _entity) public view returns (bool) { return entitiesVesting[_entity].createdDate != 0; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { ITRENStaking } from "../Interfaces/ITRENStaking.sol"; import { TrenMath, DECIMAL_PRECISION } from "../Dependencies/TrenMath.sol"; import { SafetyTransfer } from "../Dependencies/SafetyTransfer.sol"; contract TRENStaking is ITRENStaking, PausableUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; // ------------------------------------------- State ------------------------------------------ string public constant NAME = "TRENStaking"; address public debtToken; address public feeCollector; address public treasury; IERC20 public trenToken; address[] private assetsList; uint256 public totalTRENStaked; uint256 public totalDebtTokenFee; mapping(address user => Snapshot) private snapshots; mapping(address user => uint256 amount) private stakes; mapping(address asset => bool tracked) private isAssetTracked; mapping(address user => uint256 assetFeeAmount) private assetsFee; mapping(address asset => uint256 sentToTreasury) private sentAssetFeeToTreasury; bool public isSetupInitialized; // ------------------------------------------ Modifiers --------------------------------------- modifier onlyFeeCollector() { if (msg.sender != feeCollector) { revert TRENStaking__OnlyFeeCollector(msg.sender, feeCollector); } _; } modifier isPaused(address _token, uint256 _amount) { if (paused()) { sendToTreasury(_token, _amount); revert TRENStaking__StakingOnPause(); } _; } // ------------------------------------------ Initializer ------------------------------------- function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __ReentrancyGuard_init(); __Pausable_init(); _pause(); } // ------------------------------------------ External Functions ------------------------------ function setAddresses( address _debtToken, address _feeCollector, address _trenToken, address _treasury ) external onlyOwner { if (isSetupInitialized) revert TRENStaking__SetupAlreadyInitialized(); if ( _debtToken == address(0) || _feeCollector == address(0) || _trenToken == address(0) || _treasury == address(0) ) revert TRENStaking__InvalidAddresses(); debtToken = _debtToken; feeCollector = _feeCollector; trenToken = IERC20(_trenToken); treasury = _treasury; isSetupInitialized = true; } function pause() external onlyOwner { _pause(); } function unpause() external onlyOwner { _unpause(); } function stake(uint256 _TRENamount) external nonReentrant whenNotPaused { if (_TRENamount == 0) revert TRENStaking__InvalidAmount(0); uint256 currentStake = stakes[msg.sender]; uint256 assetLength = assetsList.length; address asset; for (uint256 i = 0; i < assetLength; i++) { asset = assetsList[i]; if (currentStake != 0) { if (i == 0) { checkDebtTokenGain(); } checkAssetGain(asset); } _updateUserSnapshots(asset, msg.sender); } uint256 newStake = currentStake + _TRENamount; stakes[msg.sender] = newStake; totalTRENStaked += _TRENamount; emit TotalTRENStakedUpdated(totalTRENStaked); trenToken.safeTransferFrom(msg.sender, address(this), _TRENamount); emit StakeUpdated(msg.sender, newStake); } function unstake(uint256 _TRENamount) external nonReentrant { uint256 currentStake = stakes[msg.sender]; if (currentStake == 0) revert TRENStaking__InvalidStakeAmount(0); uint256 assetLength = assetsList.length; address asset; for (uint256 i = 0; i < assetLength; i++) { asset = assetsList[i]; if (i == 0) { checkDebtTokenGain(); } checkAssetGain(asset); _updateUserSnapshots(asset, msg.sender); } if (_TRENamount > 0) { uint256 trenToWithdraw = TrenMath._min(_TRENamount, currentStake); uint256 newStake = currentStake - trenToWithdraw; stakes[msg.sender] = newStake; totalTRENStaked -= trenToWithdraw; emit TotalTRENStakedUpdated(totalTRENStaked); trenToken.safeTransfer(msg.sender, trenToWithdraw); emit StakeUpdated(msg.sender, newStake); } } function increaseFeeAsset( address _asset, uint256 _assetFee ) external onlyFeeCollector isPaused(_asset, _assetFee) { if (!isAssetTracked[_asset]) { isAssetTracked[_asset] = true; assetsList.push(_asset); } uint256 assetFeePerTRENStaked = 0; if (totalTRENStaked > 0) assetFeePerTRENStaked = calculateFeePerTRENStaked(_assetFee); assetsFee[_asset] += assetFeePerTRENStaked; emit AssetFeeUpdated(_asset, assetsFee[_asset]); } function increaseFeeDebtToken(uint256 _debtTokenFee) external onlyFeeCollector isPaused(debtToken, _debtTokenFee) { uint256 debtTokenFeePerTRENStaked = 0; if (totalTRENStaked > 0) { debtTokenFeePerTRENStaked = calculateFeePerTRENStaked(_debtTokenFee); } totalDebtTokenFee += debtTokenFeePerTRENStaked; emit TotalDebtTokenFeeUpdated(totalDebtTokenFee); } function getPendingAssetGain(address _asset, address _user) external view returns (uint256) { return _getPendingAssetGain(_asset, _user); } function getPendingDebtTokenGain(address _user) external view returns (uint256) { return _getPendingDebtTokenGain(_user); } function getAssetsList() external view returns (address[] memory) { return assetsList; } // ------------------------------------------ Private functions ------------------------------ function _updateUserSnapshots(address _asset, address _user) private { snapshots[_user].assetsFeeSnapshot[_asset] = assetsFee[_asset]; snapshots[_user].debtTokenFeeSnapshot = totalDebtTokenFee; emit StakerSnapshotsUpdated(_user, assetsFee[_asset], totalDebtTokenFee); } function _sendAssetGainToUser(address _asset, uint256 _assetGain) private { _assetGain = SafetyTransfer.decimalsCorrection(_asset, _assetGain); _sendAsset(msg.sender, _asset, _assetGain); emit SentAsset(_asset, msg.sender, _assetGain); } function sendToTreasury(address _asset, uint256 _amount) private { _sendAsset(treasury, _asset, _amount); sentAssetFeeToTreasury[_asset] += _amount; emit SentAssetFeeToTreasury(_asset, _amount); } function checkDebtTokenGain() private { uint256 debtTokenGain = _getPendingDebtTokenGain(msg.sender); if (debtTokenGain != 0) { _sendAsset(msg.sender, debtToken, debtTokenGain); emit StakingDebtTokenGainWithdrawn(msg.sender, debtTokenGain); } } function _sendAsset(address _receiver, address _asset, uint256 _amount) private { IERC20(_asset).safeTransfer(_receiver, _amount); } function checkAssetGain(address _asset) private { uint256 assetGain = _getPendingDebtTokenGain(msg.sender); if (assetGain != 0) { _sendAssetGainToUser(_asset, assetGain); emit StakingAssetGainWithdrawn(msg.sender, _asset, assetGain); } } function calculateFeePerTRENStaked(uint256 _feeAmount) private view returns (uint256) { return (_feeAmount * DECIMAL_PRECISION) / totalTRENStaked; } function _getPendingAssetGain(address _asset, address _user) private view returns (uint256) { uint256 assetFeeSnapshot = snapshots[_user].assetsFeeSnapshot[_asset]; return (stakes[_user] * (assetsFee[_asset] - assetFeeSnapshot)) / DECIMAL_PRECISION; } function _getPendingDebtTokenGain(address _user) private view returns (uint256) { uint256 debtTokenFeeSnapshot = snapshots[_user].debtTokenFeeSnapshot; return (stakes[_user] * (totalDebtTokenFee - debtTokenFeeSnapshot)) / DECIMAL_PRECISION; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; contract TRENToken is ERC20Permit { string public constant NAME = "TREN"; string public constant SYMBOL = "TREN"; uint256 internal constant _1_MILLION = 1e24; // 1e6 * 1e18 = 1e24 address public immutable treasury; constructor(address _treasurySig) ERC20(NAME, SYMBOL) ERC20Permit(NAME) { require(_treasurySig != address(0), "Invalid Treasury Sig"); treasury = _treasurySig; //Lazy Mint to setup protocol. //After the deployment scripts, deployer addr automatically send the fund to the treasury. _mint(msg.sender, _1_MILLION * 50); _mint(_treasurySig, _1_MILLION * 50); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { TrenBase } from "./Dependencies/TrenBase.sol"; import { TrenMath, DECIMAL_PRECISION } from "./Dependencies/TrenMath.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; import { IActivePool } from "./Interfaces/IActivePool.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; import { ICollSurplusPool } from "./Interfaces/ICollSurplusPool.sol"; import { IStabilityPool } from "./Interfaces/IStabilityPool.sol"; import { IDefaultPool } from "./Interfaces/IDefaultPool.sol"; import { ISortedTrenBoxes } from "./Interfaces/ISortedTrenBoxes.sol"; import { ITrenBoxManager } from "./Interfaces/ITrenBoxManager.sol"; import { IFeeCollector } from "./Interfaces/IFeeCollector.sol"; contract TrenBoxManager is ITrenBoxManager, UUPSUpgradeable, ReentrancyGuardUpgradeable, TrenBase { // Constants // ------------------------------------------------------------------------------------------------------ string public constant NAME = "TrenBoxManager"; uint256 public constant SECONDS_IN_ONE_MINUTE = 60; /* * Half-life of 12h. 12h = 720 min * (1/2) = d^720 => d = (1/2)^(1/720) */ uint256 public constant MINUTE_DECAY_FACTOR = 999_037_758_833_783_000; /* * BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption. * Corresponds to (1 / ALPHA) in the white paper. */ uint256 public constant BETA = 2; // Structs // -------------------------------------------------------------------------------------------------------- // Object containing the asset and debt token snapshots for a given active trenBox struct RewardSnapshot { uint256 asset; uint256 debt; } // State // ---------------------------------------------------------------------------------------------------------- mapping(address collateral => uint256 rate) public baseRate; // The timestamp of the latest fee operation (redemption or new debt token issuance) mapping(address collateral => uint256 feeOpeartionTimestamp) public lastFeeOperationTime; mapping(address borrower => mapping(address collateral => TrenBox)) public TrenBoxes; mapping(address collateral => uint256 totalStake) public totalStakes; // Snapshot of the value of totalStakes, taken immediately after the latest liquidation mapping(address collateral => uint256 totalStake) public totalStakesSnapshot; // Snapshot of the total collateral across the ActivePool and DefaultPool, immediately after the // latest liquidation. mapping(address collateral => uint256 totalCollateral) public totalCollateralSnapshot; /* * L_Colls and L_Debts track the sums of accumulated liquidation rewards per unit staked. During its lifetime, each stake earns: * * An asset gain of ( stake * [L_Colls - L_Colls(0)] ) * A debt increase of ( stake * [L_Debts - L_Debts(0)] ) * * Where L_Colls(0) and L_Debts(0) are snapshots of L_Colls and L_Debts for the active TrenBox taken at the instant the stake was made */ mapping(address collateral => uint256 accumulatedRewards) public L_Colls; mapping(address collateral => uint256 accumulatedRewards) public L_Debts; // Map addresses with active trenBoxes to their RewardSnapshot mapping(address borrower => mapping(address collateral => RewardSnapshot)) public rewardSnapshots; // Array of all active trenBox addresses - used to to compute an approximate hint off-chain, for // the sorted list insertion mapping(address collateral => address[] owners) public trenBoxOwners; // Error trackers for the trenBox redistribution calculation mapping(address collateral => uint256 collateralError) public lastCollError_Redistribution; mapping(address collateral => uint256 debtError) public lastDebtError_Redistribution; // Modifiers // ------------------------------------------------------------------------------------------------------ modifier onlyTrenBoxManagerOperations() { if (msg.sender != trenBoxManagerOperations) { revert TrenBoxManager__OnlyTrenBoxManagerOperations(); } _; } modifier onlyBorrowerOperations() { if (msg.sender != borrowerOperations) { revert TrenBoxManager__OnlyBorrowerOperations(); } _; } modifier onlyTrenBoxManagerOperationsOrBorrowerOperations() { if (msg.sender != borrowerOperations && msg.sender != trenBoxManagerOperations) { revert TrenBoxManager__OnlyTrenBoxManagerOperationsOrBorrowerOperations(); } _; } // Initializer // ------------------------------------------------------------------------------------------------------ function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); __ReentrancyGuard_init(); } // External/public functions // -------------------------------------------------------------------------------------- function isValidFirstRedemptionHint( address _asset, address _firstRedemptionHint, uint256 _price ) external view returns (bool) { if ( _firstRedemptionHint == address(0) || !ISortedTrenBoxes(sortedTrenBoxes).contains(_asset, _firstRedemptionHint) || getCurrentICR(_asset, _firstRedemptionHint, _price) < IAdminContract(adminContract).getMcr(_asset) ) { return false; } address nextTrenBox = ISortedTrenBoxes(sortedTrenBoxes).getNext(_asset, _firstRedemptionHint); return nextTrenBox == address(0) || getCurrentICR(_asset, nextTrenBox, _price) < IAdminContract(adminContract).getMcr(_asset); } // Return the nominal collateral ratio (ICR) of a given TrenBox, without the price. Takes a // trenBox's pending coll and debt rewards from redistributions into account. function getNominalICR( address _asset, address _borrower ) external view override returns (uint256) { (uint256 currentAsset, uint256 currentDebt) = _getCurrentTrenBoxAmounts(_asset, _borrower); uint256 NICR = TrenMath._computeNominalCR(currentAsset, currentDebt); return NICR; } // Return the current collateral ratio (ICR) of a given TrenBox. Takes a trenBox's pending coll // and debt rewards from redistributions into account. function getCurrentICR( address _asset, address _borrower, uint256 _price ) public view override returns (uint256) { (uint256 currentAsset, uint256 currentDebt) = _getCurrentTrenBoxAmounts(_asset, _borrower); uint256 ICR = TrenMath._computeCR(currentAsset, currentDebt, _price); return ICR; } // Get the borrower's pending accumulated asset reward, earned by their stake function getPendingAssetReward( address _asset, address _borrower ) public view override returns (uint256) { uint256 snapshotAsset = rewardSnapshots[_borrower][_asset].asset; uint256 rewardPerUnitStaked = L_Colls[_asset] - snapshotAsset; if (rewardPerUnitStaked == 0 || !isTrenBoxActive(_asset, _borrower)) { return 0; } uint256 stake = TrenBoxes[_borrower][_asset].stake; uint256 pendingAssetReward = (stake * rewardPerUnitStaked) / DECIMAL_PRECISION; return pendingAssetReward; } // Get the borrower's pending accumulated debt token reward, earned by their stake function getPendingDebtTokenReward( address _asset, address _borrower ) public view override returns (uint256) { uint256 snapshotDebt = rewardSnapshots[_borrower][_asset].debt; uint256 rewardPerUnitStaked = L_Debts[_asset] - snapshotDebt; if (rewardPerUnitStaked == 0 || !isTrenBoxActive(_asset, _borrower)) { return 0; } uint256 stake = TrenBoxes[_borrower][_asset].stake; return (stake * rewardPerUnitStaked) / DECIMAL_PRECISION; } function hasPendingRewards( address _asset, address _borrower ) public view override returns (bool) { if (!isTrenBoxActive(_asset, _borrower)) { return false; } return (rewardSnapshots[_borrower][_asset].asset < L_Colls[_asset]); } function getEntireDebtAndColl( address _asset, address _borrower ) external view override returns (uint256 debt, uint256 coll, uint256 pendingDebtReward, uint256 pendingCollReward) { pendingDebtReward = getPendingDebtTokenReward(_asset, _borrower); pendingCollReward = getPendingAssetReward(_asset, _borrower); TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; debt = trenBox.debt + pendingDebtReward; coll = trenBox.coll + pendingCollReward; } function isTrenBoxActive( address _asset, address _borrower ) public view override returns (bool) { return getTrenBoxStatus(_asset, _borrower) == uint256(Status.active); } function getTCR(address _asset, uint256 _price) external view override returns (uint256) { return _getTCR(_asset, _price); } function checkRecoveryMode( address _asset, uint256 _price ) external view override returns (bool) { return _checkRecoveryMode(_asset, _price); } function getBorrowingRate(address _asset) external view override returns (uint256) { return IAdminContract(adminContract).getBorrowingFee(_asset); } function getBorrowingFee( address _asset, uint256 _debt ) external view override returns (uint256) { return (IAdminContract(adminContract).getBorrowingFee(_asset) * _debt) / DECIMAL_PRECISION; } function getRedemptionFee(address _asset, uint256 _assetDraw) public view returns (uint256) { return _calcRedemptionFee(getRedemptionRate(_asset), _assetDraw); } function getRedemptionFeeWithDecay( address _asset, uint256 _assetDraw ) external view override returns (uint256) { return _calcRedemptionFee(getRedemptionRateWithDecay(_asset), _assetDraw); } function getRedemptionRate(address _asset) public view override returns (uint256) { return _calcRedemptionRate(_asset, baseRate[_asset]); } function getRedemptionRateWithDecay(address _asset) public view override returns (uint256) { return _calcRedemptionRate(_asset, _calcDecayedBaseRate(_asset)); } // Called by Tren contracts // ------------------------------------------------------------------------------------ function addTrenBoxOwnerToArray( address _asset, address _borrower ) external override onlyBorrowerOperations returns (uint256 index) { address[] storage assetOwners = trenBoxOwners[_asset]; assetOwners.push(_borrower); index = assetOwners.length - 1; TrenBoxes[_borrower][_asset].arrayIndex = uint128(index); return index; } function executeFullRedemption( address _asset, address _borrower, uint256 _newColl ) external override nonReentrant onlyTrenBoxManagerOperations { _removeStake(_asset, _borrower); _closeTrenBox(_asset, _borrower, Status.closedByRedemption); _redeemCloseTrenBox( _asset, _borrower, IAdminContract(adminContract).getDebtTokenGasCompensation(_asset), _newColl ); IFeeCollector(feeCollector).closeDebt(_borrower, _asset); emit TrenBoxUpdated(_asset, _borrower, 0, 0, 0, TrenBoxManagerOperation.redeemCollateral); } function executePartialRedemption( address _asset, address _borrower, uint256 _newDebt, uint256 _newColl, uint256 _newNICR, address _upperPartialRedemptionHint, address _lowerPartialRedemptionHint ) external override onlyTrenBoxManagerOperations { ISortedTrenBoxes(sortedTrenBoxes).reInsert( _asset, _borrower, _newNICR, _upperPartialRedemptionHint, _lowerPartialRedemptionHint ); TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; uint256 paybackFraction = ((trenBox.debt - _newDebt) * 1 ether) / trenBox.debt; if (paybackFraction != 0) { IFeeCollector(feeCollector).decreaseDebt(_borrower, _asset, paybackFraction); } trenBox.debt = _newDebt; trenBox.coll = _newColl; _updateStakeAndTotalStakes(_asset, _borrower); emit TrenBoxUpdated( _asset, _borrower, _newDebt, _newColl, trenBox.stake, TrenBoxManagerOperation.redeemCollateral ); } function finalizeRedemption( address _asset, address _receiver, uint256 _debtToRedeem, uint256 _assetFeeAmount, uint256 _assetRedeemedAmount ) external override onlyTrenBoxManagerOperations { // Send the asset fee if (_assetFeeAmount != 0) { address destination = IFeeCollector(feeCollector).getProtocolRevenueDestination(); IActivePool(activePool).sendAsset(_asset, destination, _assetFeeAmount); IFeeCollector(feeCollector).handleRedemptionFee(_asset, _assetFeeAmount); } // Burn the total debt tokens that is cancelled with debt, and send the redeemed asset to // msg.sender IDebtToken(debtToken).burn(_receiver, _debtToRedeem); // Update Active Pool, and send asset to account uint256 collToSendToRedeemer = _assetRedeemedAmount - _assetFeeAmount; IActivePool(activePool).decreaseDebt(_asset, _debtToRedeem); IActivePool(activePool).sendAsset(_asset, _receiver, collToSendToRedeemer); } function updateBaseRateFromRedemption( address _asset, uint256 _assetDrawn, uint256 _price, uint256 _totalDebtTokenSupply ) external onlyTrenBoxManagerOperations { uint256 decayedBaseRate = _calcDecayedBaseRate(_asset); uint256 redeemedDebtFraction = (_assetDrawn * _price) / _totalDebtTokenSupply; uint256 newBaseRate = decayedBaseRate + (redeemedDebtFraction / BETA); newBaseRate = TrenMath._min(newBaseRate, DECIMAL_PRECISION); assert(newBaseRate > 0); baseRate[_asset] = newBaseRate; emit BaseRateUpdated(_asset, newBaseRate); _updateLastFeeOpTime(_asset); } function applyPendingRewards( address _asset, address _borrower ) external override nonReentrant onlyTrenBoxManagerOperationsOrBorrowerOperations { return _applyPendingRewards(_asset, _borrower); } // Move a TrenBox's pending debt and collateral rewards from distributions, from the Default // Pool // to the Active Pool function movePendingTrenBoxRewardsToActivePool( address _asset, uint256 _debt, uint256 _assetAmount ) external override onlyTrenBoxManagerOperations { _movePendingTrenBoxRewardsToActivePool(_asset, _debt, _assetAmount); } // Update borrower's snapshots of L_Colls and L_Debts to reflect the current values function updateTrenBoxRewardSnapshots( address _asset, address _borrower ) external override onlyBorrowerOperations { return _updateTrenBoxRewardSnapshots(_asset, _borrower); } function updateStakeAndTotalStakes( address _asset, address _borrower ) external override onlyBorrowerOperations returns (uint256) { return _updateStakeAndTotalStakes(_asset, _borrower); } function removeStake( address _asset, address _borrower ) external override onlyTrenBoxManagerOperationsOrBorrowerOperations { return _removeStake(_asset, _borrower); } function redistributeDebtAndColl( address _asset, uint256 _debt, uint256 _coll, uint256 _debtToOffset, uint256 _collToSendToStabilityPool ) external override nonReentrant onlyTrenBoxManagerOperations { IStabilityPool(stabilityPool).offset(_debtToOffset, _asset, _collToSendToStabilityPool); if (_debt == 0) { return; } /* * Add distributed coll and debt rewards-per-unit-staked to the running totals. Division uses a "feedback" * error correction, to keep the cumulative error low in the running totals L_Colls and L_Debts: * * 1) Form numerators which compensate for the floor division errors that occurred the last time this * function was called. * 2) Calculate "per-unit-staked" ratios. * 3) Multiply each ratio back by its denominator, to reveal the current floor division error. * 4) Store these errors for use in the next correction when this function is called. * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended. */ uint256 collNumerator = (_coll * DECIMAL_PRECISION) + lastCollError_Redistribution[_asset]; uint256 debtNumerator = (_debt * DECIMAL_PRECISION) + lastDebtError_Redistribution[_asset]; // Get the per-unit-staked terms uint256 assetStakes = totalStakes[_asset]; uint256 collRewardPerUnitStaked = collNumerator / assetStakes; uint256 debtRewardPerUnitStaked = debtNumerator / assetStakes; lastCollError_Redistribution[_asset] = collNumerator - (collRewardPerUnitStaked * assetStakes); lastDebtError_Redistribution[_asset] = debtNumerator - (debtRewardPerUnitStaked * assetStakes); // Add per-unit-staked terms to the running totals uint256 liquidatedColl = L_Colls[_asset] + collRewardPerUnitStaked; uint256 liquidatedDebt = L_Debts[_asset] + debtRewardPerUnitStaked; L_Colls[_asset] = liquidatedColl; L_Debts[_asset] = liquidatedDebt; emit LTermsUpdated(_asset, liquidatedColl, liquidatedDebt); IActivePool(activePool).decreaseDebt(_asset, _debt); IDefaultPool(defaultPool).increaseDebt(_asset, _debt); IActivePool(activePool).sendAsset(_asset, defaultPool, _coll); } function updateSystemSnapshots_excludeCollRemainder( address _asset, uint256 _collRemainder ) external onlyTrenBoxManagerOperations { uint256 totalStakesCached = totalStakes[_asset]; totalStakesSnapshot[_asset] = totalStakesCached; uint256 activeColl = IActivePool(activePool).getAssetBalance(_asset); uint256 liquidatedColl = IDefaultPool(defaultPool).getAssetBalance(_asset); uint256 _totalCollateralSnapshot = activeColl - _collRemainder + liquidatedColl; totalCollateralSnapshot[_asset] = _totalCollateralSnapshot; emit SystemSnapshotsUpdated(_asset, totalStakesCached, _totalCollateralSnapshot); } function closeTrenBox( address _asset, address _borrower ) external override onlyTrenBoxManagerOperationsOrBorrowerOperations { return _closeTrenBox(_asset, _borrower, Status.closedByOwner); } function closeTrenBoxLiquidation( address _asset, address _borrower ) external override onlyTrenBoxManagerOperations { _closeTrenBox(_asset, _borrower, Status.closedByLiquidation); IFeeCollector(feeCollector).liquidateDebt(_borrower, _asset); emit TrenBoxUpdated( _asset, _borrower, 0, 0, 0, TrenBoxManagerOperation.liquidateInNormalMode ); } function sendGasCompensation( address _asset, address _liquidator, uint256 _debtTokenAmount, uint256 _assetAmount ) external nonReentrant onlyTrenBoxManagerOperations { if (_debtTokenAmount != 0) { IDebtToken(debtToken).returnFromPool(gasPoolAddress, _liquidator, _debtTokenAmount); } if (_assetAmount != 0) { IActivePool(activePool).sendAsset(_asset, _liquidator, _assetAmount); } } // Internal functions // --------------------------------------------------------------------------------------------- function _redeemCloseTrenBox( address _asset, address _borrower, uint256 _debtTokenAmount, uint256 _assetAmount ) internal { IDebtToken(debtToken).burn(gasPoolAddress, _debtTokenAmount); // Update Active Pool, and send asset to account IActivePool(activePool).decreaseDebt(_asset, _debtTokenAmount); // send asset from Active Pool to CollSurplus Pool ICollSurplusPool(collSurplusPool).accountSurplus(_asset, _borrower, _assetAmount); IActivePool(activePool).sendAsset(_asset, collSurplusPool, _assetAmount); } function _movePendingTrenBoxRewardsToActivePool( address _asset, uint256 _debtTokenAmount, uint256 _assetAmount ) internal { IDefaultPool(defaultPool).decreaseDebt(_asset, _debtTokenAmount); IActivePool(activePool).increaseDebt(_asset, _debtTokenAmount); IDefaultPool(defaultPool).sendAssetToActivePool(_asset, _assetAmount); } function _getCurrentTrenBoxAmounts( address _asset, address _borrower ) internal view returns (uint256 coll, uint256 debt) { uint256 pendingCollReward = getPendingAssetReward(_asset, _borrower); uint256 pendingDebtReward = getPendingDebtTokenReward(_asset, _borrower); TrenBox memory trenBox = TrenBoxes[_borrower][_asset]; coll = trenBox.coll + pendingCollReward; debt = trenBox.debt + pendingDebtReward; } // Add the borrowers's coll and debt rewards earned from redistributions, to their TrenBox function _applyPendingRewards(address _asset, address _borrower) internal { if (!hasPendingRewards(_asset, _borrower)) { return; } // Compute pending rewards uint256 pendingCollReward = getPendingAssetReward(_asset, _borrower); uint256 pendingDebtReward = getPendingDebtTokenReward(_asset, _borrower); // Apply pending rewards to trenBox's state TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; trenBox.coll = trenBox.coll + pendingCollReward; trenBox.debt = trenBox.debt + pendingDebtReward; _updateTrenBoxRewardSnapshots(_asset, _borrower); // Transfer from DefaultPool to ActivePool _movePendingTrenBoxRewardsToActivePool(_asset, pendingDebtReward, pendingCollReward); emit TrenBoxUpdated( _asset, _borrower, trenBox.debt, trenBox.coll, trenBox.stake, TrenBoxManagerOperation.applyPendingRewards ); } function _updateTrenBoxRewardSnapshots(address _asset, address _borrower) internal { uint256 liquidatedColl = L_Colls[_asset]; uint256 liquidatedDebt = L_Debts[_asset]; RewardSnapshot storage snapshot = rewardSnapshots[_borrower][_asset]; snapshot.asset = liquidatedColl; snapshot.debt = liquidatedDebt; emit TrenBoxSnapshotsUpdated(_asset, liquidatedColl, liquidatedDebt); } function _removeStake(address _asset, address _borrower) internal { TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; totalStakes[_asset] -= trenBox.stake; trenBox.stake = 0; } // Update borrower's stake based on their latest collateral value function _updateStakeAndTotalStakes( address _asset, address _borrower ) internal returns (uint256) { TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; uint256 newStake = _computeNewStake(_asset, trenBox.coll); uint256 oldStake = trenBox.stake; trenBox.stake = newStake; uint256 newTotal = totalStakes[_asset] - oldStake + newStake; totalStakes[_asset] = newTotal; emit TotalStakesUpdated(_asset, newTotal); return newStake; } // Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at // the last liquidation function _computeNewStake( address _asset, uint256 _coll ) internal view returns (uint256 stake) { uint256 assetColl = totalCollateralSnapshot[_asset]; if (assetColl == 0) { stake = _coll; } else { uint256 assetStakes = totalStakesSnapshot[_asset]; /* * The following assert() holds true because: * - The system always contains >= 1 trenBox * - When we close or liquidate a trenBox, we redistribute the pending rewards, so if all trenBoxes were closed/liquidated, * rewards would’ve been emptied and totalCollateralSnapshot would be zero too. */ assert(assetStakes != 0); stake = (_coll * assetStakes) / assetColl; } } function _closeTrenBox(address _asset, address _borrower, Status closedStatus) internal { assert(closedStatus != Status.nonExistent && closedStatus != Status.active); uint256 TrenBoxOwnersArrayLength = trenBoxOwners[_asset].length; if (TrenBoxOwnersArrayLength <= 1 || ISortedTrenBoxes(sortedTrenBoxes).getSize(_asset) <= 1) { revert TrenBoxManager__OnlyOneTrenBox(); } TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; trenBox.status = closedStatus; trenBox.coll = 0; trenBox.debt = 0; RewardSnapshot storage rewardSnapshot = rewardSnapshots[_borrower][_asset]; rewardSnapshot.asset = 0; rewardSnapshot.debt = 0; _removeTrenBoxOwner(_asset, _borrower, TrenBoxOwnersArrayLength); ISortedTrenBoxes(sortedTrenBoxes).remove(_asset, _borrower); } function _removeTrenBoxOwner( address _asset, address _borrower, uint256 TrenBoxOwnersArrayLength ) internal { TrenBox memory trenBox = TrenBoxes[_borrower][_asset]; assert(trenBox.status != Status.nonExistent && trenBox.status != Status.active); uint128 index = trenBox.arrayIndex; uint256 length = TrenBoxOwnersArrayLength; uint256 idxLast = length - 1; assert(index <= idxLast); address[] storage trenBoxAssetOwners = trenBoxOwners[_asset]; address addressToMove = trenBoxAssetOwners[idxLast]; trenBoxAssetOwners[index] = addressToMove; TrenBoxes[addressToMove][_asset].arrayIndex = index; emit TrenBoxIndexUpdated(_asset, addressToMove, index); trenBoxAssetOwners.pop(); } function _calcRedemptionRate( address _asset, uint256 _baseRate ) internal view returns (uint256) { return TrenMath._min( IAdminContract(adminContract).getRedemptionFeeFloor(_asset) + _baseRate, DECIMAL_PRECISION ); } function _calcRedemptionFee( uint256 _redemptionRate, uint256 _assetDraw ) internal pure returns (uint256) { uint256 redemptionFee = (_redemptionRate * _assetDraw) / DECIMAL_PRECISION; if (redemptionFee >= _assetDraw) { revert TrenBoxManager__FeeBiggerThanAssetDraw(); } return redemptionFee; } function _updateLastFeeOpTime(address _asset) internal { uint256 timePassed = block.timestamp - lastFeeOperationTime[_asset]; if (timePassed >= SECONDS_IN_ONE_MINUTE) { // Update the last fee operation time only if time passed >= decay interval. This // prevents base rate griefing. lastFeeOperationTime[_asset] = block.timestamp; emit LastFeeOpTimeUpdated(_asset, block.timestamp); } } function _calcDecayedBaseRate(address _asset) internal view returns (uint256) { uint256 minutesPassed = _minutesPassedSinceLastFeeOp(_asset); uint256 decayFactor = TrenMath._decPow(MINUTE_DECAY_FACTOR, minutesPassed); return (baseRate[_asset] * decayFactor) / DECIMAL_PRECISION; } function _minutesPassedSinceLastFeeOp(address _asset) internal view returns (uint256) { return (block.timestamp - lastFeeOperationTime[_asset]) / SECONDS_IN_ONE_MINUTE; } // --- TrenBox property getters // -------------------------------------------------------------------------------------- function getTrenBoxStatus( address _asset, address _borrower ) public view override returns (uint256) { return uint256(TrenBoxes[_borrower][_asset].status); } function getTrenBoxStake( address _asset, address _borrower ) external view override returns (uint256) { return TrenBoxes[_borrower][_asset].stake; } function getTrenBoxDebt( address _asset, address _borrower ) external view override returns (uint256) { return TrenBoxes[_borrower][_asset].debt; } function getTrenBoxColl( address _asset, address _borrower ) external view override returns (uint256) { return TrenBoxes[_borrower][_asset].coll; } function getTrenBoxOwnersCount(address _asset) external view override returns (uint256) { return trenBoxOwners[_asset].length; } function getTrenBoxFromTrenBoxOwnersArray( address _asset, uint256 _index ) external view override returns (address) { return trenBoxOwners[_asset][_index]; } function getNetDebt(address _asset, uint256 _debt) external view returns (uint256) { return _getNetDebt(_asset, _debt); } // --- TrenBox property setters, called by Tren's // BorrowerOperations/VMRedemptions/VMLiquidations --------------- function setTrenBoxStatus( address _asset, address _borrower, uint256 _num ) external override onlyBorrowerOperations { TrenBoxes[_borrower][_asset].status = Status(_num); } function increaseTrenBoxColl( address _asset, address _borrower, uint256 _collIncrease ) external override onlyBorrowerOperations returns (uint256 newColl) { TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; newColl = trenBox.coll + _collIncrease; trenBox.coll = newColl; } function decreaseTrenBoxColl( address _asset, address _borrower, uint256 _collDecrease ) external override onlyBorrowerOperations returns (uint256 newColl) { TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; newColl = trenBox.coll - _collDecrease; trenBox.coll = newColl; } function increaseTrenBoxDebt( address _asset, address _borrower, uint256 _debtIncrease ) external override onlyBorrowerOperations returns (uint256 newDebt) { TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; newDebt = trenBox.debt + _debtIncrease; trenBox.debt = newDebt; } function decreaseTrenBoxDebt( address _asset, address _borrower, uint256 _debtDecrease ) external override onlyBorrowerOperations returns (uint256) { TrenBox storage trenBox = TrenBoxes[_borrower][_asset]; uint256 oldDebt = trenBox.debt; if (_debtDecrease == 0) { return oldDebt; // no changes } uint256 paybackFraction = (_debtDecrease * 1 ether) / oldDebt; uint256 newDebt = oldDebt - _debtDecrease; trenBox.debt = newDebt; if (paybackFraction != 0) { IFeeCollector(feeCollector).decreaseDebt(_borrower, _asset, paybackFraction); } return newDebt; } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { TrenBase } from "./Dependencies/TrenBase.sol"; import { TrenMath, DECIMAL_PRECISION } from "./Dependencies/TrenMath.sol"; import { IAdminContract } from "./Interfaces/IAdminContract.sol"; import { IActivePool } from "./Interfaces/IActivePool.sol"; import { ICollSurplusPool } from "./Interfaces/ICollSurplusPool.sol"; import { IDebtToken } from "./Interfaces/IDebtToken.sol"; import { IPriceFeed } from "./Interfaces/IPriceFeed.sol"; import { ISortedTrenBoxes } from "./Interfaces/ISortedTrenBoxes.sol"; import { IStabilityPool } from "./Interfaces/IStabilityPool.sol"; import { ITrenBoxManager } from "./Interfaces/ITrenBoxManager.sol"; import { ITrenBoxManagerOperations } from "./Interfaces/ITrenBoxManagerOperations.sol"; contract TrenBoxManagerOperations is ITrenBoxManagerOperations, UUPSUpgradeable, ReentrancyGuardUpgradeable, TrenBase { string public constant NAME = "TrenBoxManagerOperations"; uint256 public constant PERCENTAGE_PRECISION = 10_000; uint256 public constant BATCH_SIZE_LIMIT = 25; uint256 public redemptionSofteningParam; // Initializer // ------------------------------------------------------------------------------------------------------ function initialize() public initializer { address initialOwner = _msgSender(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); __ReentrancyGuard_init(); } // Liquidation external functions // ----------------------------------------------------------------------------------- /** * @notice Single liquidation function. * Closes the trenBox if its ICR is lower than the minimum collateral ratio. */ function liquidate(address _asset, address _borrower) external override { if (!ITrenBoxManager(trenBoxManager).isTrenBoxActive(_asset, _borrower)) { revert TrenBoxManagerOperations__TrenBoxNotActive(); } address[] memory borrowers = new address[](1); borrowers[0] = _borrower; batchLiquidateTrenBoxes(_asset, borrowers); } /** * @notice Liquidate a sequence of trenBoxes. * Closes a maximum number of n under-collateralized TrenBoxes, * starting from the one with the lowest collateral ratio in the system, and moving upwards. */ function liquidateTrenBoxes(address _asset, uint256 _n) external override nonReentrant { LocalVariables_OuterLiquidationFunction memory vars; LiquidationTotals memory totals; vars.price = IPriceFeed(priceFeed).fetchPrice(_asset); vars.debtTokenInStabPool = IStabilityPool(stabilityPool).getTotalDebtTokenDeposits(); vars.recoveryModeAtStart = _checkRecoveryMode(_asset, vars.price); // Perform the appropriate liquidation sequence - tally the values, and obtain their totals if (vars.recoveryModeAtStart) { totals = _getTotalsFromLiquidateTrenBoxesSequence_RecoveryMode( _asset, vars.price, vars.debtTokenInStabPool, _n ); } else { totals = _getTotalsFromLiquidateTrenBoxesSequence_NormalMode( _asset, vars.price, vars.debtTokenInStabPool, _n ); } if (totals.totalDebtInSequence == 0) { revert TrenBoxManagerOperations__NothingToLiquidate(); } ITrenBoxManager(trenBoxManager).redistributeDebtAndColl( _asset, totals.totalDebtToRedistribute, totals.totalCollToRedistribute, totals.totalDebtToOffset, totals.totalCollToSendToSP ); if (totals.totalCollSurplus != 0) { IActivePool(activePool).sendAsset(_asset, collSurplusPool, totals.totalCollSurplus); } ITrenBoxManager(trenBoxManager).updateSystemSnapshots_excludeCollRemainder( _asset, totals.totalCollGasCompensation ); vars.liquidatedDebt = totals.totalDebtInSequence; vars.liquidatedColl = totals.totalCollInSequence - totals.totalCollGasCompensation - totals.totalCollSurplus; emit Liquidation( _asset, vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalDebtTokenGasCompensation ); ITrenBoxManager(trenBoxManager).sendGasCompensation( _asset, msg.sender, totals.totalDebtTokenGasCompensation, totals.totalCollGasCompensation ); } /** * @notice Attempt to liquidate a custom list of trenBoxes provided by the caller. */ function batchLiquidateTrenBoxes( address _asset, address[] memory _trenBoxArray ) public override nonReentrant { if (_trenBoxArray.length == 0 || _trenBoxArray.length > BATCH_SIZE_LIMIT) { revert TrenBoxManagerOperations__InvalidArraySize(); } LocalVariables_OuterLiquidationFunction memory vars; LiquidationTotals memory totals; vars.debtTokenInStabPool = IStabilityPool(stabilityPool).getTotalDebtTokenDeposits(); vars.price = IPriceFeed(priceFeed).fetchPrice(_asset); vars.recoveryModeAtStart = _checkRecoveryMode(_asset, vars.price); // Perform the appropriate liquidation sequence - tally values and obtain their totals. if (vars.recoveryModeAtStart) { totals = _getTotalFromBatchLiquidate_RecoveryMode( _asset, vars.price, vars.debtTokenInStabPool, _trenBoxArray ); } else { totals = _getTotalsFromBatchLiquidate_NormalMode( _asset, vars.price, vars.debtTokenInStabPool, _trenBoxArray ); } if (totals.totalDebtInSequence == 0) { revert TrenBoxManagerOperations__NothingToLiquidate(); } ITrenBoxManager(trenBoxManager).redistributeDebtAndColl( _asset, totals.totalDebtToRedistribute, totals.totalCollToRedistribute, totals.totalDebtToOffset, totals.totalCollToSendToSP ); if (totals.totalCollSurplus != 0) { IActivePool(activePool).sendAsset(_asset, collSurplusPool, totals.totalCollSurplus); } // Update system snapshots ITrenBoxManager(trenBoxManager).updateSystemSnapshots_excludeCollRemainder( _asset, totals.totalCollGasCompensation ); vars.liquidatedDebt = totals.totalDebtInSequence; vars.liquidatedColl = totals.totalCollInSequence - totals.totalCollGasCompensation - totals.totalCollSurplus; emit Liquidation( _asset, vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalDebtTokenGasCompensation ); ITrenBoxManager(trenBoxManager).sendGasCompensation( _asset, msg.sender, totals.totalDebtTokenGasCompensation, totals.totalCollGasCompensation ); } // Redemption external functions // ------------------------------------------------------------------------------------ function redeemCollateral( address _asset, uint256 _debtTokenAmount, address _upperPartialRedemptionHint, address _lowerPartialRedemptionHint, address _firstRedemptionHint, uint256 _partialRedemptionHintNICR, uint256 _maxIterations, uint256 _maxFeePercentage ) external override nonReentrant { RedemptionTotals memory totals; totals.price = IPriceFeed(priceFeed).fetchPrice(_asset); _validateRedemptionRequirements(_asset, _maxFeePercentage, _debtTokenAmount, totals.price); totals.totalDebtTokenSupplyAtStart = getEntireSystemDebt(_asset); totals.remainingDebt = _debtTokenAmount; address currentBorrower; if ( ITrenBoxManager(trenBoxManager).isValidFirstRedemptionHint( _asset, _firstRedemptionHint, totals.price ) ) { currentBorrower = _firstRedemptionHint; } else { currentBorrower = ISortedTrenBoxes(sortedTrenBoxes).getLast(_asset); // Find the first trenBox with ICR >= MCR while ( currentBorrower != address(0) && ITrenBoxManager(trenBoxManager).getCurrentICR( _asset, currentBorrower, totals.price ) < IAdminContract(adminContract).getMcr(_asset) ) { currentBorrower = ISortedTrenBoxes(sortedTrenBoxes).getPrev(_asset, currentBorrower); } } // Loop through the trenBoxes starting from the one with lowest collateral ratio until // _debtTokenAmount is exchanged for collateral if (_maxIterations == 0) { _maxIterations = type(uint256).max; } while (currentBorrower != address(0) && totals.remainingDebt != 0 && _maxIterations != 0) { _maxIterations--; // Save the address of the trenBox preceding the current one, before potentially // modifying the list address nextUserToCheck = ISortedTrenBoxes(sortedTrenBoxes).getPrev(_asset, currentBorrower); ITrenBoxManager(trenBoxManager).applyPendingRewards(_asset, currentBorrower); SingleRedemptionValues memory singleRedemption = _redeemCollateralFromTrenBox( _asset, currentBorrower, totals.remainingDebt, totals.price, _upperPartialRedemptionHint, _lowerPartialRedemptionHint, _partialRedemptionHintNICR ); if (singleRedemption.cancelledPartial) break; // Partial redemption was cancelled // (out-of-date hint, or new net debt < minimum), therefore we could not redeem from // the last trenBox totals.totalDebtToRedeem = totals.totalDebtToRedeem + singleRedemption.debtLot; totals.totalCollDrawn = totals.totalCollDrawn + singleRedemption.collLot; totals.remainingDebt = totals.remainingDebt - singleRedemption.debtLot; currentBorrower = nextUserToCheck; } if (totals.totalCollDrawn == 0) { revert TrenBoxManagerOperations__UnableToRedeemAnyAmount(); } // Decay the baseRate due to time passed, and then increase it according to the size of this // redemption. // Use the saved total GRAI supply value, from before it was reduced by the redemption. ITrenBoxManager(trenBoxManager).updateBaseRateFromRedemption( _asset, totals.totalCollDrawn, totals.price, totals.totalDebtTokenSupplyAtStart ); // Calculate the collateral fee totals.collFee = ITrenBoxManager(trenBoxManager).getRedemptionFee(_asset, totals.totalCollDrawn); _requireUserAcceptsFee(totals.collFee, totals.totalCollDrawn, _maxFeePercentage); ITrenBoxManager(trenBoxManager).finalizeRedemption( _asset, msg.sender, totals.totalDebtToRedeem, totals.collFee, totals.totalCollDrawn ); emit Redemption( _asset, _debtTokenAmount, totals.totalDebtToRedeem, totals.totalCollDrawn, totals.collFee ); } // Hint helper functions // -------------------------------------------------------------------------------------------- /** * @notice getRedemptionHints() - Helper function for finding the right hints to pass to * redeemCollateral(). * * It simulates a redemption of `_debtTokenAmount` to figure out where the redemption sequence * will start and what state the final TrenBox of the sequence will end up in. * * Returns three hints: * - `firstRedemptionHint` is the address of the first TrenBox with ICR >= MCR (i.e. the first * TrenBox that will be redeemed). * - `partialRedemptionHintNICR` is the final nominal ICR of the last TrenBox of the sequence * after being hit by partial redemption, or zero in case of no partial redemption. * - `truncatedDebtTokenAmount` is the maximum amount that can be redeemed out of the the * provided `_debtTokenAmount`. This can be lower than `_debtTokenAmount` when redeeming * the full amount would leave the last TrenBox of the redemption sequence with less net * debt * than the minimum allowed value (i.e. IAdminContract(adminContract).MIN_NET_DEBT()). * * The number of TrenBoxes to consider for redemption can be capped by passing a non-zero value * as `_maxIterations`, while passing zero will leave it uncapped. */ function getRedemptionHints( address _asset, uint256 _debtTokenAmount, uint256 _price, uint256 _maxIterations ) external view override returns ( address firstRedemptionHint, uint256 partialRedemptionHintNewICR, uint256 truncatedDebtTokenAmount ) { HintHelperLocalVars memory vars = HintHelperLocalVars({ asset: _asset, debtTokenAmount: _debtTokenAmount, price: _price, maxIterations: _maxIterations }); uint256 remainingDebt = _debtTokenAmount; address currentTrenBoxBorrower = ISortedTrenBoxes(sortedTrenBoxes).getLast(vars.asset); while ( currentTrenBoxBorrower != address(0) && ITrenBoxManager(trenBoxManager).getCurrentICR( vars.asset, currentTrenBoxBorrower, vars.price ) < IAdminContract(adminContract).getMcr(vars.asset) ) { currentTrenBoxBorrower = ISortedTrenBoxes(sortedTrenBoxes).getPrev(vars.asset, currentTrenBoxBorrower); } firstRedemptionHint = currentTrenBoxBorrower; if (vars.maxIterations == 0) { vars.maxIterations = type(uint256).max; } while ( currentTrenBoxBorrower != address(0) && remainingDebt != 0 && vars.maxIterations-- != 0 ) { uint256 currentTrenBoxNetDebt = _getNetDebt( vars.asset, ITrenBoxManager(trenBoxManager).getTrenBoxDebt(vars.asset, currentTrenBoxBorrower) + ITrenBoxManager(trenBoxManager).getPendingDebtTokenReward( vars.asset, currentTrenBoxBorrower ) ); if (currentTrenBoxNetDebt <= remainingDebt) { remainingDebt = remainingDebt - currentTrenBoxNetDebt; } else { if (currentTrenBoxNetDebt > IAdminContract(adminContract).getMinNetDebt(vars.asset)) { uint256 maxRedeemableDebt = TrenMath._min( remainingDebt, currentTrenBoxNetDebt - IAdminContract(adminContract).getMinNetDebt(vars.asset) ); uint256 currentTrenBoxColl = ITrenBoxManager(trenBoxManager).getTrenBoxColl( vars.asset, currentTrenBoxBorrower ) + ITrenBoxManager(trenBoxManager).getPendingAssetReward( vars.asset, currentTrenBoxBorrower ); uint256 collLot = (maxRedeemableDebt * DECIMAL_PRECISION) / vars.price; // Apply redemption softening collLot = (collLot * redemptionSofteningParam) / PERCENTAGE_PRECISION; uint256 newColl = currentTrenBoxColl - collLot; uint256 newDebt = currentTrenBoxNetDebt - maxRedeemableDebt; uint256 compositeDebt = _getCompositeDebt(vars.asset, newDebt); partialRedemptionHintNewICR = TrenMath._computeNominalCR(newColl, compositeDebt); remainingDebt = remainingDebt - maxRedeemableDebt; } break; } currentTrenBoxBorrower = ISortedTrenBoxes(sortedTrenBoxes).getPrev(vars.asset, currentTrenBoxBorrower); } truncatedDebtTokenAmount = _debtTokenAmount - remainingDebt; } /** * @notice getApproxHint() - return address of a TrenBox that is, on average, (length / * numTrials) * positions away in the sortedTrenBoxes list from the correct insert position of the TrenBox * to be inserted. * * Note: The output address is worst-case O(n) positions away from the correct insert position, * however, the function is probabilistic. * Input can be tuned to guarantee results to a high degree of confidence, * e.g: * Submitting numTrials = k * sqrt(length), with k = 15 makes it very, very likely that the * ouput * address will be <= sqrt(length) positions away from the correct insert position. */ function getApproxHint( address _asset, uint256 _CR, uint256 _numTrials, uint256 _inputRandomSeed ) external view override returns (address hintAddress, uint256 diff, uint256 latestRandomSeed) { uint256 arrayLength = ITrenBoxManager(trenBoxManager).getTrenBoxOwnersCount(_asset); if (arrayLength == 0) { return (address(0), 0, _inputRandomSeed); } hintAddress = ISortedTrenBoxes(sortedTrenBoxes).getLast(_asset); diff = TrenMath._getAbsoluteDifference( _CR, ITrenBoxManager(trenBoxManager).getNominalICR(_asset, hintAddress) ); latestRandomSeed = _inputRandomSeed; uint256 i = 1; while (i < _numTrials) { latestRandomSeed = uint256(keccak256(abi.encodePacked(latestRandomSeed))); uint256 arrayIndex = latestRandomSeed % arrayLength; address currentAddress = ITrenBoxManager(trenBoxManager).getTrenBoxFromTrenBoxOwnersArray(_asset, arrayIndex); uint256 currentNICR = ITrenBoxManager(trenBoxManager).getNominalICR(_asset, currentAddress); // check if abs(current - CR) > abs(closest - CR), and update closest if current is // closer uint256 currentDiff = TrenMath._getAbsoluteDifference(currentNICR, _CR); if (currentDiff < diff) { diff = currentDiff; hintAddress = currentAddress; } i++; } } function computeNominalCR( uint256 _coll, uint256 _debt ) external pure override returns (uint256) { return TrenMath._computeNominalCR(_coll, _debt); } // Liquidation internal/helper functions // ---------------------------------------------------------------------------- /** * @notice This function is used when the batch liquidation sequence starts during Recovery * Mode. * However, it handles the case where the system *leaves* Recovery Mode, part way * through the liquidation sequence */ function _getTotalFromBatchLiquidate_RecoveryMode( address _asset, uint256 _price, uint256 _debtTokenInStabPool, address[] memory _trenBoxArray ) internal returns (LiquidationTotals memory totals) { LocalVariables_LiquidationSequence memory vars; LiquidationValues memory singleLiquidation; vars.remainingDebtTokenInStabPool = _debtTokenInStabPool; vars.backToNormalMode = false; vars.entireSystemDebt = getEntireSystemDebt(_asset); vars.entireSystemColl = getEntireSystemColl(_asset); for (uint256 i = 0; i < _trenBoxArray.length;) { vars.user = _trenBoxArray[i]; // Skip non-active trenBoxes if ( ITrenBoxManager(trenBoxManager).getTrenBoxStatus(_asset, vars.user) != uint256(ITrenBoxManager.Status.active) ) { unchecked { ++i; } continue; } vars.ICR = ITrenBoxManager(trenBoxManager).getCurrentICR(_asset, vars.user, _price); if (!vars.backToNormalMode) { // Skip this trenBox if ICR is greater than MCR and Stability Pool is empty if ( vars.ICR >= IAdminContract(adminContract).getMcr(_asset) && vars.remainingDebtTokenInStabPool == 0 ) { unchecked { ++i; } continue; } uint256 TCR = TrenMath._computeCR(vars.entireSystemColl, vars.entireSystemDebt, _price); singleLiquidation = _liquidateRecoveryMode( _asset, vars.user, vars.ICR, vars.remainingDebtTokenInStabPool, TCR, _price ); // Update aggregate trackers vars.remainingDebtTokenInStabPool = vars.remainingDebtTokenInStabPool - singleLiquidation.debtToOffset; vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToOffset; vars.entireSystemColl = vars.entireSystemColl - singleLiquidation.collToSendToSP - singleLiquidation.collGasCompensation - singleLiquidation.collSurplus; // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); vars.backToNormalMode = !_checkPotentialRecoveryMode( _asset, vars.entireSystemColl, vars.entireSystemDebt, _price ); } else if ( vars.backToNormalMode && vars.ICR < IAdminContract(adminContract).getMcr(_asset) ) { singleLiquidation = _liquidateNormalMode(_asset, vars.user, vars.remainingDebtTokenInStabPool); vars.remainingDebtTokenInStabPool = vars.remainingDebtTokenInStabPool - singleLiquidation.debtToOffset; // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); } unchecked { ++i; } } } function _getTotalsFromBatchLiquidate_NormalMode( address _asset, uint256 _price, uint256 _debtTokenInStabPool, address[] memory _trenBoxArray ) internal returns (LiquidationTotals memory totals) { LocalVariables_LiquidationSequence memory vars; LiquidationValues memory singleLiquidation; vars.remainingDebtTokenInStabPool = _debtTokenInStabPool; for (uint256 i = 0; i < _trenBoxArray.length;) { vars.user = _trenBoxArray[i]; vars.ICR = ITrenBoxManager(trenBoxManager).getCurrentICR(_asset, vars.user, _price); if (vars.ICR < IAdminContract(adminContract).getMcr(_asset)) { singleLiquidation = _liquidateNormalMode(_asset, vars.user, vars.remainingDebtTokenInStabPool); vars.remainingDebtTokenInStabPool = vars.remainingDebtTokenInStabPool - singleLiquidation.debtToOffset; // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); } unchecked { ++i; } } } function _addLiquidationValuesToTotals( LiquidationTotals memory oldTotals, LiquidationValues memory singleLiquidation ) internal pure returns (LiquidationTotals memory newTotals) { // Tally all the values with their respective running totals newTotals.totalCollGasCompensation = oldTotals.totalCollGasCompensation + singleLiquidation.collGasCompensation; newTotals.totalDebtTokenGasCompensation = oldTotals.totalDebtTokenGasCompensation + singleLiquidation.debtTokenGasCompensation; newTotals.totalDebtInSequence = oldTotals.totalDebtInSequence + singleLiquidation.entireTrenBoxDebt; newTotals.totalCollInSequence = oldTotals.totalCollInSequence + singleLiquidation.entireTrenBoxColl; newTotals.totalDebtToOffset = oldTotals.totalDebtToOffset + singleLiquidation.debtToOffset; newTotals.totalCollToSendToSP = oldTotals.totalCollToSendToSP + singleLiquidation.collToSendToSP; newTotals.totalDebtToRedistribute = oldTotals.totalDebtToRedistribute + singleLiquidation.debtToRedistribute; newTotals.totalCollToRedistribute = oldTotals.totalCollToRedistribute + singleLiquidation.collToRedistribute; newTotals.totalCollSurplus = oldTotals.totalCollSurplus + singleLiquidation.collSurplus; return newTotals; } function _getTotalsFromLiquidateTrenBoxesSequence_NormalMode( address _asset, uint256 _price, uint256 _debtTokenInStabPool, uint256 _n ) internal returns (LiquidationTotals memory totals) { LocalVariables_LiquidationSequence memory vars; LiquidationValues memory singleLiquidation; vars.remainingDebtTokenInStabPool = _debtTokenInStabPool; for (uint256 i = 0; i < _n;) { vars.user = ISortedTrenBoxes(sortedTrenBoxes).getLast(_asset); vars.ICR = ITrenBoxManager(trenBoxManager).getCurrentICR(_asset, vars.user, _price); if (vars.ICR < IAdminContract(adminContract).getMcr(_asset)) { singleLiquidation = _liquidateNormalMode(_asset, vars.user, vars.remainingDebtTokenInStabPool); vars.remainingDebtTokenInStabPool = vars.remainingDebtTokenInStabPool - singleLiquidation.debtToOffset; // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); } else { break; } // break if the loop reaches a TrenBox with ICR >= MCR unchecked { ++i; } } } function _liquidateNormalMode( address _asset, address _borrower, uint256 _debtTokenInStabPool ) internal returns (LiquidationValues memory singleLiquidation) { LocalVariables_InnerSingleLiquidateFunction memory vars; ( singleLiquidation.entireTrenBoxDebt, singleLiquidation.entireTrenBoxColl, vars.pendingDebtReward, vars.pendingCollReward ) = ITrenBoxManager(trenBoxManager).getEntireDebtAndColl(_asset, _borrower); ITrenBoxManager(trenBoxManager).movePendingTrenBoxRewardsToActivePool( _asset, vars.pendingDebtReward, vars.pendingCollReward ); ITrenBoxManager(trenBoxManager).removeStake(_asset, _borrower); singleLiquidation.collGasCompensation = _getCollGasCompensation(_asset, singleLiquidation.entireTrenBoxColl); singleLiquidation.debtTokenGasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); uint256 collToLiquidate = singleLiquidation.entireTrenBoxColl - singleLiquidation.collGasCompensation; ( singleLiquidation.debtToOffset, singleLiquidation.collToSendToSP, singleLiquidation.debtToRedistribute, singleLiquidation.collToRedistribute ) = _getOffsetAndRedistributionVals( singleLiquidation.entireTrenBoxDebt, collToLiquidate, _debtTokenInStabPool ); ITrenBoxManager(trenBoxManager).closeTrenBoxLiquidation(_asset, _borrower); emit TrenBoxLiquidated( _asset, _borrower, singleLiquidation.entireTrenBoxDebt, singleLiquidation.entireTrenBoxColl, ITrenBoxManager.TrenBoxManagerOperation.liquidateInNormalMode ); return singleLiquidation; } function _liquidateRecoveryMode( address _asset, address _borrower, uint256 _ICR, uint256 _debtTokenInStabPool, uint256 _TCR, uint256 _price ) internal returns (LiquidationValues memory singleLiquidation) { LocalVariables_InnerSingleLiquidateFunction memory vars; if (ITrenBoxManager(trenBoxManager).getTrenBoxOwnersCount(_asset) <= 1) { return singleLiquidation; } // don't liquidate if last trenBox ( singleLiquidation.entireTrenBoxDebt, singleLiquidation.entireTrenBoxColl, vars.pendingDebtReward, vars.pendingCollReward ) = ITrenBoxManager(trenBoxManager).getEntireDebtAndColl(_asset, _borrower); singleLiquidation.collGasCompensation = _getCollGasCompensation(_asset, singleLiquidation.entireTrenBoxColl); singleLiquidation.debtTokenGasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); vars.collToLiquidate = singleLiquidation.entireTrenBoxColl - singleLiquidation.collGasCompensation; // If ICR <= 100%, purely redistribute the TrenBox across all active TrenBoxes if (_ICR <= IAdminContract(adminContract)._100pct()) { ITrenBoxManager(trenBoxManager).movePendingTrenBoxRewardsToActivePool( _asset, vars.pendingDebtReward, vars.pendingCollReward ); ITrenBoxManager(trenBoxManager).removeStake(_asset, _borrower); singleLiquidation.debtToOffset = 0; singleLiquidation.collToSendToSP = 0; singleLiquidation.debtToRedistribute = singleLiquidation.entireTrenBoxDebt; singleLiquidation.collToRedistribute = vars.collToLiquidate; ITrenBoxManager(trenBoxManager).closeTrenBoxLiquidation(_asset, _borrower); emit TrenBoxLiquidated( _asset, _borrower, singleLiquidation.entireTrenBoxDebt, singleLiquidation.entireTrenBoxColl, ITrenBoxManager.TrenBoxManagerOperation.liquidateInRecoveryMode ); // If 100% < ICR < MCR, offset as much as possible, and redistribute the remainder } else if ( (_ICR > IAdminContract(adminContract)._100pct()) && (_ICR < IAdminContract(adminContract).getMcr(_asset)) ) { ITrenBoxManager(trenBoxManager).movePendingTrenBoxRewardsToActivePool( _asset, vars.pendingDebtReward, vars.pendingCollReward ); ITrenBoxManager(trenBoxManager).removeStake(_asset, _borrower); ( singleLiquidation.debtToOffset, singleLiquidation.collToSendToSP, singleLiquidation.debtToRedistribute, singleLiquidation.collToRedistribute ) = _getOffsetAndRedistributionVals( singleLiquidation.entireTrenBoxDebt, vars.collToLiquidate, _debtTokenInStabPool ); ITrenBoxManager(trenBoxManager).closeTrenBoxLiquidation(_asset, _borrower); emit TrenBoxLiquidated( _asset, _borrower, singleLiquidation.entireTrenBoxDebt, singleLiquidation.entireTrenBoxColl, ITrenBoxManager.TrenBoxManagerOperation.liquidateInRecoveryMode ); /** * If 110% <= ICR < current TCR (accounting for the preceding liquidations in the * current sequence) and * there are debt tokens in the Stability Pool, only offset, with no redistribution, * but at a capped rate of 1.1 and only if the whole debt can be liquidated. * The remainder due to the capped rate will be claimable as collateral surplus. */ } else if ( (_ICR >= IAdminContract(adminContract).getMcr(_asset)) && (_ICR < _TCR) && (singleLiquidation.entireTrenBoxDebt <= _debtTokenInStabPool) ) { ITrenBoxManager(trenBoxManager).movePendingTrenBoxRewardsToActivePool( _asset, vars.pendingDebtReward, vars.pendingCollReward ); assert(_debtTokenInStabPool != 0); ITrenBoxManager(trenBoxManager).removeStake(_asset, _borrower); singleLiquidation = _getCappedOffsetVals( _asset, singleLiquidation.entireTrenBoxDebt, singleLiquidation.entireTrenBoxColl, _price ); ITrenBoxManager(trenBoxManager).closeTrenBoxLiquidation(_asset, _borrower); if (singleLiquidation.collSurplus != 0) { ICollSurplusPool(collSurplusPool).accountSurplus( _asset, _borrower, singleLiquidation.collSurplus ); } emit TrenBoxLiquidated( _asset, _borrower, singleLiquidation.entireTrenBoxDebt, singleLiquidation.collToSendToSP, ITrenBoxManager.TrenBoxManagerOperation.liquidateInRecoveryMode ); } else { LiquidationValues memory zeroVals; return zeroVals; } return singleLiquidation; } /** * @notice This function is used when the liquidateTrenBoxes sequence starts during Recovery * Mode. * However, it handles the case where the system *leaves* Recovery Mode, part way * through the liquidation sequence */ function _getTotalsFromLiquidateTrenBoxesSequence_RecoveryMode( address _asset, uint256 _price, uint256 _debtTokenInStabPool, uint256 _n ) internal returns (LiquidationTotals memory totals) { LocalVariables_LiquidationSequence memory vars; LiquidationValues memory singleLiquidation; vars.remainingDebtTokenInStabPool = _debtTokenInStabPool; vars.backToNormalMode = false; vars.entireSystemDebt = getEntireSystemDebt(_asset); vars.entireSystemColl = getEntireSystemColl(_asset); vars.user = ISortedTrenBoxes(sortedTrenBoxes).getLast(_asset); address firstUser = ISortedTrenBoxes(sortedTrenBoxes).getFirst(_asset); for (uint256 i = 0; i < _n && vars.user != firstUser;) { // we need to cache it, because current user is likely going to be deleted address nextUser = ISortedTrenBoxes(sortedTrenBoxes).getPrev(_asset, vars.user); vars.ICR = ITrenBoxManager(trenBoxManager).getCurrentICR(_asset, vars.user, _price); if (!vars.backToNormalMode) { // Break the loop if ICR is greater than MCR and Stability Pool is empty if ( vars.ICR >= IAdminContract(adminContract).getMcr(_asset) && vars.remainingDebtTokenInStabPool == 0 ) { break; } uint256 TCR = TrenMath._computeCR(vars.entireSystemColl, vars.entireSystemDebt, _price); singleLiquidation = _liquidateRecoveryMode( _asset, vars.user, vars.ICR, vars.remainingDebtTokenInStabPool, TCR, _price ); // Update aggregate trackers vars.remainingDebtTokenInStabPool = vars.remainingDebtTokenInStabPool - singleLiquidation.debtToOffset; vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToOffset; vars.entireSystemColl = vars.entireSystemColl - singleLiquidation.collToSendToSP - singleLiquidation.collGasCompensation - singleLiquidation.collSurplus; // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); vars.backToNormalMode = !_checkPotentialRecoveryMode( _asset, vars.entireSystemColl, vars.entireSystemDebt, _price ); } else if ( vars.backToNormalMode && vars.ICR < IAdminContract(adminContract).getMcr(_asset) ) { singleLiquidation = _liquidateNormalMode(_asset, vars.user, vars.remainingDebtTokenInStabPool); vars.remainingDebtTokenInStabPool = vars.remainingDebtTokenInStabPool - singleLiquidation.debtToOffset; // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); } else { break; } // break if the loop reaches a TrenBox with ICR >= MCR vars.user = nextUser; unchecked { ++i; } } } /** * @notice In a full liquidation, returns the values for a trenBox's coll and debt to be offset, * and coll and debt to be redistributed to active trenBoxes. */ function _getOffsetAndRedistributionVals( uint256 _debt, uint256 _coll, uint256 _debtTokenInStabPool ) internal pure returns ( uint256 debtToOffset, uint256 collToSendToSP, uint256 debtToRedistribute, uint256 collToRedistribute ) { if (_debtTokenInStabPool != 0) { /** * Offset as much debt & collateral as possible against the Stability Pool, and * redistribute the remainder between all active trenBoxes. * * If the trenBox's debt is larger than the deposited debt token in the Stability Pool: * * - Offset an amount of the trenBox's debt equal to the debt token in the * Stability Pool * - Send a fraction of the trenBox's collateral to the Stability Pool, equal to the * fraction of its offset debt */ debtToOffset = TrenMath._min(_debt, _debtTokenInStabPool); collToSendToSP = (_coll * debtToOffset) / _debt; debtToRedistribute = _debt - debtToOffset; collToRedistribute = _coll - collToSendToSP; } else { debtToOffset = 0; collToSendToSP = 0; debtToRedistribute = _debt; collToRedistribute = _coll; } } /** * @dev Get its offset coll/debt and coll gas comp, and close the trenBox. */ function _getCappedOffsetVals( address _asset, uint256 _entireTrenBoxDebt, uint256 _entireTrenBoxColl, uint256 _price ) internal view returns (LiquidationValues memory singleLiquidation) { singleLiquidation.entireTrenBoxDebt = _entireTrenBoxDebt; singleLiquidation.entireTrenBoxColl = _entireTrenBoxColl; uint256 cappedCollPortion = (_entireTrenBoxDebt * IAdminContract(adminContract).getMcr(_asset)) / _price; singleLiquidation.collGasCompensation = _getCollGasCompensation(_asset, cappedCollPortion); singleLiquidation.debtTokenGasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(_asset); singleLiquidation.debtToOffset = _entireTrenBoxDebt; singleLiquidation.collToSendToSP = cappedCollPortion - singleLiquidation.collGasCompensation; singleLiquidation.collSurplus = _entireTrenBoxColl - cappedCollPortion; singleLiquidation.debtToRedistribute = 0; singleLiquidation.collToRedistribute = 0; } function _checkPotentialRecoveryMode( address _asset, uint256 _entireSystemColl, uint256 _entireSystemDebt, uint256 _price ) internal view returns (bool) { uint256 TCR = TrenMath._computeCR(_entireSystemColl, _entireSystemDebt, _price); return TCR < IAdminContract(adminContract).getCcr(_asset); } // Redemption internal/helper functions // ----------------------------------------------------------------------------- function _validateRedemptionRequirements( address _asset, uint256 _maxFeePercentage, uint256 _debtTokenAmount, uint256 _price ) internal view { uint256 redemptionBlockTimestamp = IAdminContract(adminContract).getRedemptionBlockTimestamp(_asset); if (redemptionBlockTimestamp > block.timestamp) { revert TrenBoxManagerOperations__RedemptionIsBlocked(); } uint256 redemptionFeeFloor = IAdminContract(adminContract).getRedemptionFeeFloor(_asset); if (_maxFeePercentage < redemptionFeeFloor || _maxFeePercentage > DECIMAL_PRECISION) { revert TrenBoxManagerOperations__FeePercentOutOfBounds( redemptionFeeFloor, DECIMAL_PRECISION ); } if (_debtTokenAmount == 0) { revert TrenBoxManagerOperations__EmptyAmount(); } uint256 redeemerBalance = IDebtToken(debtToken).balanceOf(msg.sender); if (redeemerBalance < _debtTokenAmount) { revert TrenBoxManagerOperations__InsufficientDebtTokenBalance(redeemerBalance); } uint256 tcr = _getTCR(_asset, _price); uint256 mcr = IAdminContract(adminContract).getMcr(_asset); if (tcr < mcr) { revert TrenBoxManagerOperations__TCRMustBeAboveMCR(tcr, mcr); } } /// @notice Redeem as much collateral as possible from _borrower's trenBox in exchange for GRAI /// up to _maxDebtTokenAmount function _redeemCollateralFromTrenBox( address _asset, address _borrower, uint256 _maxDebtTokenAmount, uint256 _price, address _upperPartialRedemptionHint, address _lowerPartialRedemptionHint, uint256 _partialRedemptionHintNICR ) internal returns (SingleRedemptionValues memory singleRedemption) { uint256 trenBoxDebt = ITrenBoxManager(trenBoxManager).getTrenBoxDebt(_asset, _borrower); uint256 trenBoxColl = ITrenBoxManager(trenBoxManager).getTrenBoxColl(_asset, _borrower); // Determine the remaining amount (lot) to be redeemed, capped by the entire debt of the // trenBox minus the liquidation reserve singleRedemption.debtLot = TrenMath._min( _maxDebtTokenAmount, trenBoxDebt - IAdminContract(adminContract).getDebtTokenGasCompensation(_asset) ); // Get the debtToken lot of equivalent value in USD singleRedemption.collLot = (singleRedemption.debtLot * DECIMAL_PRECISION) / _price; // Apply redemption softening singleRedemption.collLot = (singleRedemption.collLot * redemptionSofteningParam) / PERCENTAGE_PRECISION; // Decrease the debt and collateral of the current trenBox according to the debt token lot // and corresponding coll to send uint256 newDebt = trenBoxDebt - singleRedemption.debtLot; uint256 newColl = trenBoxColl - singleRedemption.collLot; if (newDebt == IAdminContract(adminContract).getDebtTokenGasCompensation(_asset)) { ITrenBoxManager(trenBoxManager).executeFullRedemption(_asset, _borrower, newColl); } else { uint256 newNICR = TrenMath._computeNominalCR(newColl, newDebt); /** * If the provided hint is out of date, we bail since trying to reinsert without a good * hint will almost certainly result in running out of gas. * * If the resultant net debt of the partial is less than the minimum, net debt we bail. */ if ( newNICR != _partialRedemptionHintNICR || _getNetDebt(_asset, newDebt) < IAdminContract(adminContract).getMinNetDebt(_asset) ) { singleRedemption.cancelledPartial = true; return singleRedemption; } ITrenBoxManager(trenBoxManager).executePartialRedemption( _asset, _borrower, newDebt, newColl, newNICR, _upperPartialRedemptionHint, _lowerPartialRedemptionHint ); } return singleRedemption; } function setRedemptionSofteningParam(uint256 _redemptionSofteningParam) public { if (msg.sender != timelockAddress) { revert TrenBoxManagerOperations__NotTimelock(); } if (_redemptionSofteningParam < 9700 || _redemptionSofteningParam > PERCENTAGE_PRECISION) { revert TrenBoxManagerOperations__InvalidParam(); } redemptionSofteningParam = _redemptionSofteningParam; emit RedemptionSoftenParamChanged(_redemptionSofteningParam); } function authorizeUpgrade(address newImplementation) public { _authorizeUpgrade(newImplementation); } function _authorizeUpgrade(address) internal override onlyOwner { } }
{ "metadata": { "bytecodeHash": "none", "useLiteralContent": true }, "optimizer": { "enabled": true, "runs": 200 }, "evmVersion": "paris", "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": { "": { "__CACHE_BREAKER__": "0x00000000d41867734bbee4c6863d9255b2b06ac1" } } }
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ERC2612ExpiredSignature","type":"error"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"ERC2612InvalidSigner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"currentNonce","type":"uint256"}],"name":"InvalidAccountNonce","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"_decimals","type":"uint8"}],"name":"setDecimals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
6101606040526008805460ff191660121790553480156200001f57600080fd5b5060405180604001604052806008815260200167115490c815195cdd60c21b81525080604051806040016040528060018152602001603160f81b81525060405180604001604052806008815260200167115490c815195cdd60c21b815250604051806040016040528060038152602001621514d560ea1b8152508160039081620000aa9190620002a6565b506004620000b98282620002a6565b50620000cb915083905060056200017a565b61012052620000dc8160066200017a565b61014052815160208084019190912060e052815190820120610100524660a0526200016a60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b60805250503060c05250620003e8565b60006020835110156200019a576200019283620001b3565b9050620001ad565b81620001a78482620002a6565b5060ff90505b92915050565b600080829050601f81511115620001ea578260405163305a27a960e01b8152600401620001e1919062000372565b60405180910390fd5b8051620001f782620003c3565b179392505050565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200022a57607f821691505b6020821081036200024b57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620002a1576000816000526020600020601f850160051c810160208610156200027c5750805b601f850160051c820191505b818110156200029d5782815560010162000288565b5050505b505050565b81516001600160401b03811115620002c257620002c2620001ff565b620002da81620002d3845462000215565b8462000251565b602080601f831160018114620003125760008415620002f95750858301515b600019600386901b1c1916600185901b1785556200029d565b600085815260208120601f198616915b82811015620003435788860151825594840194600190910190840162000322565b5085821015620003625787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006020808352835180602085015260005b81811015620003a25785810183015185820160400152820162000384565b506000604082860101526040601f19601f8301168501019250505092915050565b805160208083015191908110156200024b5760001960209190910360031b1b16919050565b60805160a05160c05160e051610100516101205161014051610f1262000443600039600061071a015260006106ed0152600061065f0152600061063701526000610592015260006105bc015260006105e60152610f126000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c806370a082311161009757806395d89b411161006657806395d89b411461020d578063a9059cbb14610215578063d505accf14610228578063dd62ed3e1461023b57600080fd5b806370a08231146101925780637a1395aa146101bb5780637ecebe00146101df57806384b0196e146101f257600080fd5b806323b872dd116100d357806323b872dd1461014d578063313ce567146101605780633644e5151461017557806340c10f191461017d57600080fd5b806306fdde03146100fa578063095ea7b31461011857806318160ddd1461013b575b600080fd5b610102610274565b60405161010f9190610c7b565b60405180910390f35b61012b610126366004610cb1565b610306565b604051901515815260200161010f565b6002545b60405190815260200161010f565b61012b61015b366004610cdb565b610320565b60085460405160ff909116815260200161010f565b61013f610337565b61019061018b366004610cb1565b610346565b005b61013f6101a0366004610d17565b6001600160a01b031660009081526020819052604090205490565b6101906101c9366004610d43565b6008805460ff191660ff92909216919091179055565b61013f6101ed366004610d17565b610354565b6101fa610372565b60405161010f9796959493929190610d5e565b6101026103b8565b61012b610223366004610cb1565b6103c7565b610190610236366004610df7565b6103d5565b61013f610249366004610e61565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60606003805461028390610e94565b80601f01602080910402602001604051908101604052809291908181526020018280546102af90610e94565b80156102fc5780601f106102d1576101008083540402835291602001916102fc565b820191906000526020600020905b8154815290600101906020018083116102df57829003601f168201915b5050505050905090565b600033610314818585610514565b60019150505b92915050565b600061032d848484610526565b5060019392505050565b6000610341610585565b905090565b61035082826106b0565b5050565b6001600160a01b03811660009081526007602052604081205461031a565b6000606080600080600060606103866106e6565b61038e610713565b60408051600080825260208201909252600f60f81b9b939a50919850469750309650945092509050565b60606004805461028390610e94565b600033610314818585610526565b834211156103fe5760405163313c898160e11b8152600481018590526024015b60405180910390fd5b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c988888861044b8c6001600160a01b0316600090815260076020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e00160405160208183030381529060405280519060200120905060006104a682610740565b905060006104b68287878761076d565b9050896001600160a01b0316816001600160a01b0316146104fd576040516325c0072360e11b81526001600160a01b0380831660048301528b1660248201526044016103f5565b6105088a8a8a610514565b50505050505050505050565b610521838383600161079b565b505050565b6001600160a01b03831661055057604051634b637e8f60e11b8152600060048201526024016103f5565b6001600160a01b03821661057a5760405163ec442f0560e01b8152600060048201526024016103f5565b610521838383610871565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156105de57507f000000000000000000000000000000000000000000000000000000000000000046145b1561060857507f000000000000000000000000000000000000000000000000000000000000000090565b610341604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b6001600160a01b0382166106da5760405163ec442f0560e01b8152600060048201526024016103f5565b61035060008383610871565b60606103417f0000000000000000000000000000000000000000000000000000000000000000600561099b565b60606103417f0000000000000000000000000000000000000000000000000000000000000000600661099b565b600061031a61074d610585565b8360405161190160f01b8152600281019290925260228201526042902090565b60008060008061077f88888888610a46565b92509250925061078f8282610b15565b50909695505050505050565b6001600160a01b0384166107c55760405163e602df0560e01b8152600060048201526024016103f5565b6001600160a01b0383166107ef57604051634a1406b160e11b8152600060048201526024016103f5565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561086b57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161086291815260200190565b60405180910390a35b50505050565b6001600160a01b03831661089c5780600260008282546108919190610ece565b9091555061090e9050565b6001600160a01b038316600090815260208190526040902054818110156108ef5760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016103f5565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661092a57600280548290039055610949565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161098e91815260200190565b60405180910390a3505050565b606060ff83146109b5576109ae83610bce565b905061031a565b8180546109c190610e94565b80601f01602080910402602001604051908101604052809291908181526020018280546109ed90610e94565b8015610a3a5780601f10610a0f57610100808354040283529160200191610a3a565b820191906000526020600020905b815481529060010190602001808311610a1d57829003601f168201915b5050505050905061031a565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115610a815750600091506003905082610b0b565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610ad5573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610b0157506000925060019150829050610b0b565b9250600091508190505b9450945094915050565b6000826003811115610b2957610b29610eef565b03610b32575050565b6001826003811115610b4657610b46610eef565b03610b645760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610b7857610b78610eef565b03610b995760405163fce698f760e01b8152600481018290526024016103f5565b6003826003811115610bad57610bad610eef565b03610350576040516335e2f38360e21b8152600481018290526024016103f5565b60606000610bdb83610c0d565b604080516020808252818301909252919250600091906020820181803683375050509182525060208101929092525090565b600060ff8216601f81111561031a57604051632cd44ac360e21b815260040160405180910390fd5b6000815180845260005b81811015610c5b57602081850181015186830182015201610c3f565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000610c8e6020830184610c35565b9392505050565b80356001600160a01b0381168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c95565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c95565b9250610d0760208501610c95565b9150604084013590509250925092565b600060208284031215610d2957600080fd5b610c8e82610c95565b803560ff81168114610cac57600080fd5b600060208284031215610d5557600080fd5b610c8e82610d32565b60ff60f81b881681526000602060e06020840152610d7f60e084018a610c35565b8381036040850152610d91818a610c35565b606085018990526001600160a01b038816608086015260a0850187905284810360c08601528551808252602080880193509091019060005b81811015610de557835183529284019291840191600101610dc9565b50909c9b505050505050505050505050565b600080600080600080600060e0888a031215610e1257600080fd5b610e1b88610c95565b9650610e2960208901610c95565b95506040880135945060608801359350610e4560808901610d32565b925060a0880135915060c0880135905092959891949750929550565b60008060408385031215610e7457600080fd5b610e7d83610c95565b9150610e8b60208401610c95565b90509250929050565b600181811c90821680610ea857607f821691505b602082108103610ec857634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561031a57634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fdfea164736f6c6343000817000a
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100f55760003560e01c806370a082311161009757806395d89b411161006657806395d89b411461020d578063a9059cbb14610215578063d505accf14610228578063dd62ed3e1461023b57600080fd5b806370a08231146101925780637a1395aa146101bb5780637ecebe00146101df57806384b0196e146101f257600080fd5b806323b872dd116100d357806323b872dd1461014d578063313ce567146101605780633644e5151461017557806340c10f191461017d57600080fd5b806306fdde03146100fa578063095ea7b31461011857806318160ddd1461013b575b600080fd5b610102610274565b60405161010f9190610c7b565b60405180910390f35b61012b610126366004610cb1565b610306565b604051901515815260200161010f565b6002545b60405190815260200161010f565b61012b61015b366004610cdb565b610320565b60085460405160ff909116815260200161010f565b61013f610337565b61019061018b366004610cb1565b610346565b005b61013f6101a0366004610d17565b6001600160a01b031660009081526020819052604090205490565b6101906101c9366004610d43565b6008805460ff191660ff92909216919091179055565b61013f6101ed366004610d17565b610354565b6101fa610372565b60405161010f9796959493929190610d5e565b6101026103b8565b61012b610223366004610cb1565b6103c7565b610190610236366004610df7565b6103d5565b61013f610249366004610e61565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60606003805461028390610e94565b80601f01602080910402602001604051908101604052809291908181526020018280546102af90610e94565b80156102fc5780601f106102d1576101008083540402835291602001916102fc565b820191906000526020600020905b8154815290600101906020018083116102df57829003601f168201915b5050505050905090565b600033610314818585610514565b60019150505b92915050565b600061032d848484610526565b5060019392505050565b6000610341610585565b905090565b61035082826106b0565b5050565b6001600160a01b03811660009081526007602052604081205461031a565b6000606080600080600060606103866106e6565b61038e610713565b60408051600080825260208201909252600f60f81b9b939a50919850469750309650945092509050565b60606004805461028390610e94565b600033610314818585610526565b834211156103fe5760405163313c898160e11b8152600481018590526024015b60405180910390fd5b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c988888861044b8c6001600160a01b0316600090815260076020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e00160405160208183030381529060405280519060200120905060006104a682610740565b905060006104b68287878761076d565b9050896001600160a01b0316816001600160a01b0316146104fd576040516325c0072360e11b81526001600160a01b0380831660048301528b1660248201526044016103f5565b6105088a8a8a610514565b50505050505050505050565b610521838383600161079b565b505050565b6001600160a01b03831661055057604051634b637e8f60e11b8152600060048201526024016103f5565b6001600160a01b03821661057a5760405163ec442f0560e01b8152600060048201526024016103f5565b610521838383610871565b6000306001600160a01b037f00000000000000000000000032e59464a2ea0085acb48323b3db96d9eef06e04161480156105de57507f0000000000000000000000000000000000000000000000000000000000aa36a746145b1561060857507f24649e90c5324676bfd62fda77a41e4d8e00d9bcf1d65ff0082693e5cb0b273e90565b610341604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f404e6ae39ea431a4900d8bfe6b715c6719bbbfea390209d8b12dde5d66a42b4e918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b6001600160a01b0382166106da5760405163ec442f0560e01b8152600060048201526024016103f5565b61035060008383610871565b60606103417f4552432054657374000000000000000000000000000000000000000000000008600561099b565b60606103417f3100000000000000000000000000000000000000000000000000000000000001600661099b565b600061031a61074d610585565b8360405161190160f01b8152600281019290925260228201526042902090565b60008060008061077f88888888610a46565b92509250925061078f8282610b15565b50909695505050505050565b6001600160a01b0384166107c55760405163e602df0560e01b8152600060048201526024016103f5565b6001600160a01b0383166107ef57604051634a1406b160e11b8152600060048201526024016103f5565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561086b57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161086291815260200190565b60405180910390a35b50505050565b6001600160a01b03831661089c5780600260008282546108919190610ece565b9091555061090e9050565b6001600160a01b038316600090815260208190526040902054818110156108ef5760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016103f5565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661092a57600280548290039055610949565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161098e91815260200190565b60405180910390a3505050565b606060ff83146109b5576109ae83610bce565b905061031a565b8180546109c190610e94565b80601f01602080910402602001604051908101604052809291908181526020018280546109ed90610e94565b8015610a3a5780601f10610a0f57610100808354040283529160200191610a3a565b820191906000526020600020905b815481529060010190602001808311610a1d57829003601f168201915b5050505050905061031a565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115610a815750600091506003905082610b0b565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610ad5573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610b0157506000925060019150829050610b0b565b9250600091508190505b9450945094915050565b6000826003811115610b2957610b29610eef565b03610b32575050565b6001826003811115610b4657610b46610eef565b03610b645760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610b7857610b78610eef565b03610b995760405163fce698f760e01b8152600481018290526024016103f5565b6003826003811115610bad57610bad610eef565b03610350576040516335e2f38360e21b8152600481018290526024016103f5565b60606000610bdb83610c0d565b604080516020808252818301909252919250600091906020820181803683375050509182525060208101929092525090565b600060ff8216601f81111561031a57604051632cd44ac360e21b815260040160405180910390fd5b6000815180845260005b81811015610c5b57602081850181015186830182015201610c3f565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000610c8e6020830184610c35565b9392505050565b80356001600160a01b0381168114610cac57600080fd5b919050565b60008060408385031215610cc457600080fd5b610ccd83610c95565b946020939093013593505050565b600080600060608486031215610cf057600080fd5b610cf984610c95565b9250610d0760208501610c95565b9150604084013590509250925092565b600060208284031215610d2957600080fd5b610c8e82610c95565b803560ff81168114610cac57600080fd5b600060208284031215610d5557600080fd5b610c8e82610d32565b60ff60f81b881681526000602060e06020840152610d7f60e084018a610c35565b8381036040850152610d91818a610c35565b606085018990526001600160a01b038816608086015260a0850187905284810360c08601528551808252602080880193509091019060005b81811015610de557835183529284019291840191600101610dc9565b50909c9b505050505050505050505050565b600080600080600080600060e0888a031215610e1257600080fd5b610e1b88610c95565b9650610e2960208901610c95565b95506040880135945060608801359350610e4560808901610d32565b925060a0880135915060c0880135905092959891949750929550565b60008060408385031215610e7457600080fd5b610e7d83610c95565b9150610e8b60208401610c95565b90509250929050565b600181811c90821680610ea857607f821691505b602082108103610ec857634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561031a57634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fdfea164736f6c6343000817000a
[ Download: CSV Export ]
[ Download: CSV Export ]
A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.