Source Code
Latest 25 from a total of 225 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Issue | 28852866 | 56 days ago | IN | 0 FRAX | 0.00000298 | ||||
| Issue | 28852840 | 56 days ago | IN | 0 FRAX | 0.00000318 | ||||
| Issue | 28852810 | 56 days ago | IN | 0 FRAX | 0.00000331 | ||||
| Issue | 28852682 | 56 days ago | IN | 0 FRAX | 0.00000317 | ||||
| Issue | 28780757 | 57 days ago | IN | 0 FRAX | 0.00000339 | ||||
| Issue | 28780736 | 57 days ago | IN | 0 FRAX | 0.00000375 | ||||
| Issue | 28780716 | 57 days ago | IN | 0 FRAX | 0.00000358 | ||||
| Issue | 28780695 | 57 days ago | IN | 0 FRAX | 0.00000338 | ||||
| Issue | 28780673 | 57 days ago | IN | 0 FRAX | 0.00000355 | ||||
| Issue | 28780652 | 57 days ago | IN | 0 FRAX | 0.00000316 | ||||
| Issue | 28780630 | 57 days ago | IN | 0 FRAX | 0.00000325 | ||||
| Issue | 28780611 | 57 days ago | IN | 0 FRAX | 0.00000354 | ||||
| Issue | 28780590 | 57 days ago | IN | 0 FRAX | 0.0000037 | ||||
| Issue | 28780560 | 57 days ago | IN | 0 FRAX | 0.00000327 | ||||
| Issue | 28780494 | 57 days ago | IN | 0 FRAX | 0.00000332 | ||||
| Issue | 28780475 | 57 days ago | IN | 0 FRAX | 0.00000377 | ||||
| Issue | 28780443 | 57 days ago | IN | 0 FRAX | 0.00000329 | ||||
| Issue | 28780403 | 57 days ago | IN | 0 FRAX | 0.00000336 | ||||
| Issue | 28780385 | 57 days ago | IN | 0 FRAX | 0.00000351 | ||||
| Issue | 28780360 | 57 days ago | IN | 0 FRAX | 0.00000349 | ||||
| Issue | 28780342 | 57 days ago | IN | 0 FRAX | 0.00000339 | ||||
| Issue | 28780312 | 57 days ago | IN | 0 FRAX | 0.00000374 | ||||
| Issue | 28694889 | 59 days ago | IN | 0 FRAX | 0.00000452 | ||||
| Issue | 28597490 | 61 days ago | IN | 0 FRAX | 0.00012202 | ||||
| Issue | 28597464 | 61 days ago | IN | 0 FRAX | 0.00010689 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Contract Name:
IssuerV2
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 200 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "@openzeppelin/contracts-5/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts-5/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-5/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts-5/utils/Pausable.sol";
import "@openzeppelin/contracts-5/utils/math/Math.sol";
import "contracts/lending/core/interfaces/IAaveOracle.sol";
import "contracts/token/IERC20Stablecoin.sol";
import "./CollateralVault.sol";
import "./AmoManager.sol";
import "./OracleAware.sol";
/**
* @title IssuerV2
* @notice Extended issuer responsible for issuing dUSD tokens with asset-level minting overrides and global pause
*/
contract IssuerV2 is AccessControl, OracleAware, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20Metadata;
/* Core state */
IERC20Stablecoin public dusd;
uint8 public immutable dusdDecimals;
CollateralVault public collateralVault;
AmoManager public amoManager;
/* Events */
event CollateralVaultSet(address indexed collateralVault);
event AmoManagerSet(address indexed amoManager);
event AssetMintingPauseUpdated(address indexed asset, bool paused);
/* Roles */
bytes32 public constant AMO_MANAGER_ROLE = keccak256("AMO_MANAGER_ROLE");
bytes32 public constant INCENTIVES_MANAGER_ROLE =
keccak256("INCENTIVES_MANAGER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/* Errors */
error SlippageTooHigh(uint256 minDUsd, uint256 dusdAmount);
error IssuanceSurpassesExcessCollateral(
uint256 collateralInDusd,
uint256 circulatingDusd
);
error MintingToAmoShouldNotIncreaseSupply(
uint256 circulatingDusdBefore,
uint256 circulatingDusdAfter
);
error AssetMintingPaused(address asset);
/* Overrides */
// If true, minting with this collateral asset is paused at the issuer level
mapping(address => bool) public assetMintingPaused;
/**
* @notice Initializes the IssuerV2 contract with core dependencies
* @param _collateralVault The address of the collateral vault
* @param _dusd The address of the dUSD stablecoin
* @param oracle The address of the price oracle
* @param _amoManager The address of the AMO Manager
*/
constructor(
address _collateralVault,
address _dusd,
IPriceOracleGetter oracle,
address _amoManager
) OracleAware(oracle, oracle.BASE_CURRENCY_UNIT()) {
collateralVault = CollateralVault(_collateralVault);
dusd = IERC20Stablecoin(_dusd);
dusdDecimals = dusd.decimals();
amoManager = AmoManager(_amoManager);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
grantRole(AMO_MANAGER_ROLE, msg.sender);
grantRole(INCENTIVES_MANAGER_ROLE, msg.sender);
grantRole(PAUSER_ROLE, msg.sender);
}
/* Issuer */
/**
* @notice Issues dUSD tokens in exchange for collateral from the caller
* @param collateralAmount The amount of collateral to deposit
* @param collateralAsset The address of the collateral asset
* @param minDUsd The minimum amount of dUSD to receive, used for slippage protection
*/
function issue(
uint256 collateralAmount,
address collateralAsset,
uint256 minDUsd
) external nonReentrant whenNotPaused {
// Ensure the collateral asset is supported by the vault before any further processing
if (!collateralVault.isCollateralSupported(collateralAsset)) {
revert CollateralVault.UnsupportedCollateral(collateralAsset);
}
// Ensure the issuer has not paused this asset for minting
if (assetMintingPaused[collateralAsset]) {
revert AssetMintingPaused(collateralAsset);
}
uint8 collateralDecimals = IERC20Metadata(collateralAsset).decimals();
uint256 baseValue = Math.mulDiv(
oracle.getAssetPrice(collateralAsset),
collateralAmount,
10 ** collateralDecimals
);
uint256 dusdAmount = baseValueToDusdAmount(baseValue);
if (dusdAmount < minDUsd) {
revert SlippageTooHigh(minDUsd, dusdAmount);
}
// Transfer collateral directly to vault
IERC20Metadata(collateralAsset).safeTransferFrom(
msg.sender,
address(collateralVault),
collateralAmount
);
dusd.mint(msg.sender, dusdAmount);
}
/**
* @notice Issues dUSD tokens using excess collateral in the system
* @param receiver The address to receive the minted dUSD tokens
* @param dusdAmount The amount of dUSD to mint
*/
function issueUsingExcessCollateral(
address receiver,
uint256 dusdAmount
) external onlyRole(INCENTIVES_MANAGER_ROLE) whenNotPaused {
dusd.mint(receiver, dusdAmount);
// We don't use the buffer value here because we only mint up to the excess collateral
uint256 _circulatingDusd = circulatingDusd();
uint256 _collateralInDusd = collateralInDusd();
if (_collateralInDusd < _circulatingDusd) {
revert IssuanceSurpassesExcessCollateral(
_collateralInDusd,
_circulatingDusd
);
}
}
/**
* @notice Increases the AMO supply by minting new dUSD tokens
* @param dusdAmount The amount of dUSD to mint and send to the AMO Manager
*/
function increaseAmoSupply(
uint256 dusdAmount
) external onlyRole(AMO_MANAGER_ROLE) whenNotPaused {
uint256 _circulatingDusdBefore = circulatingDusd();
dusd.mint(address(amoManager), dusdAmount);
uint256 _circulatingDusdAfter = circulatingDusd();
// Sanity check that we are sending to the active AMO Manager
if (_circulatingDusdAfter != _circulatingDusdBefore) {
revert MintingToAmoShouldNotIncreaseSupply(
_circulatingDusdBefore,
_circulatingDusdAfter
);
}
}
/**
* @notice Calculates the circulating supply of dUSD tokens
* @return The amount of dUSD tokens that are not held by the AMO Manager
*/
function circulatingDusd() public view returns (uint256) {
uint256 totalDusd = dusd.totalSupply();
uint256 amoDusd = amoManager.totalAmoSupply();
return totalDusd - amoDusd;
}
/**
* @notice Calculates the collateral value in dUSD tokens
* @return The amount of dUSD tokens equivalent to the collateral value
*/
function collateralInDusd() public view returns (uint256) {
uint256 _collateralInBase = collateralVault.totalValue();
return baseValueToDusdAmount(_collateralInBase);
}
/**
* @notice Converts a base value to an equivalent amount of dUSD tokens
* @param baseValue The amount of base value to convert
* @return The equivalent amount of dUSD tokens
*/
function baseValueToDusdAmount(
uint256 baseValue
) public view returns (uint256) {
return Math.mulDiv(baseValue, 10 ** dusdDecimals, baseCurrencyUnit);
}
/**
* @notice Returns whether `asset` is currently enabled for minting by the issuer
* @dev Asset must be supported by the collateral vault and not paused by issuer
*/
function isAssetMintingEnabled(address asset) public view returns (bool) {
if (!collateralVault.isCollateralSupported(asset)) return false;
return !assetMintingPaused[asset];
}
/* Admin */
/**
* @notice Sets the AMO Manager address
* @param _amoManager The address of the AMO Manager
*/
function setAmoManager(
address _amoManager
) external onlyRole(DEFAULT_ADMIN_ROLE) {
address old = address(amoManager);
amoManager = AmoManager(_amoManager);
grantRole(AMO_MANAGER_ROLE, _amoManager);
if (old != address(0) && old != _amoManager) {
revokeRole(AMO_MANAGER_ROLE, old);
}
emit AmoManagerSet(_amoManager);
}
/**
* @notice Sets the collateral vault address
* @param _collateralVault The address of the collateral vault
*/
function setCollateralVault(
address _collateralVault
) external onlyRole(DEFAULT_ADMIN_ROLE) {
collateralVault = CollateralVault(_collateralVault);
emit CollateralVaultSet(_collateralVault);
}
/**
* @notice Set minting pause override for a specific collateral asset
* @param asset The collateral asset address
* @param paused True to pause minting; false to enable
*/
function setAssetMintingPause(
address asset,
bool paused
) external onlyRole(PAUSER_ROLE) {
// Optional guard: if vault does not support the asset, setting an override is meaningless
if (!collateralVault.isCollateralSupported(asset)) {
revert CollateralVault.UnsupportedCollateral(asset);
}
assetMintingPaused[asset] = paused;
emit AssetMintingPauseUpdated(asset, paused);
}
/**
* @notice Pause all minting operations
*/
function pauseMinting() external onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @notice Unpause all minting operations
*/
function unpauseMinting() external onlyRole(PAUSER_ROLE) {
_unpause();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}// 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/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// 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/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.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 Pausable is Context {
bool private _paused;
/**
* @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.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
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 {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @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 ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.
pragma solidity ^0.8.20;
import {EnumerableSet} from "./EnumerableSet.sol";
/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => bytes32) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
return map._keys.length();
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
bytes32 value = map._values[key];
if (value == bytes32(0)) {
return (contains(map, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key];
if (value == 0 && !contains(map, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values();
}
// UintToUintMap
struct UintToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(key)));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToAddressMap
struct UintToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, bytes32(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToUintMap
struct AddressToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToUintMap storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToUintMap
struct Bytes32ToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
return set(map._inner, key, bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (key, uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, key));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "@openzeppelin/contracts-5/utils/structs/EnumerableMap.sol";
import "contracts/shared/Constants.sol";
import "contracts/token/IERC20Stablecoin.sol";
import "contracts/dusd/AmoVault.sol";
/**
* @title AmoManager
* @dev Manages AMOs for dUSD
* Handles allocation, deallocation, collateral management, and profit management for AMO vaults.
*/
contract AmoManager is AccessControl, OracleAware {
using EnumerableMap for EnumerableMap.AddressToUintMap;
/* Core state */
EnumerableMap.AddressToUintMap private _amoVaults;
uint256 public totalAllocated;
IERC20Stablecoin public dusd;
CollateralVault public collateralHolderVault;
uint256 public immutable USD_UNIT;
/* Events */
event AmoVaultSet(address indexed amoVault, bool isActive);
event AmoAllocated(address indexed amoVault, uint256 dusdAmount);
event AmoDeallocated(address indexed amoVault, uint256 dusdAmount);
event ProfitsWithdrawn(address indexed amoVault, uint256 amount);
/* Roles */
bytes32 public constant AMO_ALLOCATOR_ROLE =
keccak256("AMO_ALLOCATOR_ROLE");
bytes32 public constant FEE_COLLECTOR_ROLE =
keccak256("FEE_COLLECTOR_ROLE");
/* Errors */
error InactiveAmoVault(address amoVault);
error AmoSupplyInvariantViolation(
uint256 startingSupply,
uint256 endingSupply
);
error AmoVaultAlreadyEnabled(address amoVault);
error CannotTransferDUSD();
error InsufficientProfits(
uint256 takeProfitValueInUsd,
int256 availableProfitInUsd
);
/**
* @notice Initializes the AmoManager contract.
* @param _dusd The address of the dUSD stablecoin.
* @param _collateralHolderVault The address of the collateral holder vault.
* @param _oracle The oracle for price feeds.
*/
constructor(
address _dusd,
address _collateralHolderVault,
IPriceOracleGetter _oracle
) OracleAware(_oracle, Constants.ORACLE_BASE_CURRENCY_UNIT) {
dusd = IERC20Stablecoin(_dusd);
collateralHolderVault = CollateralVault(_collateralHolderVault);
USD_UNIT = oracle.BASE_CURRENCY_UNIT();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
grantRole(AMO_ALLOCATOR_ROLE, msg.sender);
grantRole(FEE_COLLECTOR_ROLE, msg.sender);
}
/* AMO */
/**
* @notice Allocates AMO tokens to an AMO vault.
* @param amoVault The address of the AMO vault.
* @param dusdAmount The amount of dUSD to allocate.
*/
function allocateAmo(
address amoVault,
uint256 dusdAmount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
uint256 startingAmoSupply = totalAmoSupply();
// Make sure the vault is active
if (!isAmoActive(amoVault)) {
revert InactiveAmoVault(amoVault);
}
// Update the allocation for this vault
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
_amoVaults.set(amoVault, currentAllocation + dusdAmount);
// Make the deposit
totalAllocated += dusdAmount;
dusd.transfer(amoVault, dusdAmount);
// Check invariants
uint256 endingAmoSupply = totalAmoSupply();
if (endingAmoSupply != startingAmoSupply) {
revert AmoSupplyInvariantViolation(
startingAmoSupply,
endingAmoSupply
);
}
emit AmoAllocated(amoVault, dusdAmount);
}
/**
* @notice Deallocates AMO tokens from an AMO vault.
* @param amoVault The address of the AMO vault.
* @param dusdAmount The amount of dUSD to deallocate.
*/
function deallocateAmo(
address amoVault,
uint256 dusdAmount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
uint256 startingAmoSupply = totalAmoSupply();
// We don't require that the vault is active or has allocation, since we want to allow withdrawing from inactive vaults
// If the vault is still active, make sure it has enough allocation and decrease it
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
if (currentAllocation > 0) {
// Update the allocation for this vault
_amoVaults.set(amoVault, currentAllocation - dusdAmount);
}
// Make the withdrawal
totalAllocated -= dusdAmount;
dusd.transferFrom(amoVault, address(this), dusdAmount);
// Check invariants
uint256 endingAmoSupply = totalAmoSupply();
if (endingAmoSupply != startingAmoSupply) {
revert AmoSupplyInvariantViolation(
startingAmoSupply,
endingAmoSupply
);
}
emit AmoDeallocated(amoVault, dusdAmount);
}
/**
* @notice Returns the total AMO supply.
* @return The total AMO supply.
*/
function totalAmoSupply() public view returns (uint256) {
uint256 freeBalance = dusd.balanceOf(address(this));
return freeBalance + totalAllocated;
}
/**
* @notice Decreases the AMO supply by burning dUSD.
* @param dusdAmount The amount of dUSD to burn.
*/
function decreaseAmoSupply(
uint256 dusdAmount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
dusd.burn(dusdAmount);
}
/**
* @notice Checks if an AMO vault is active.
* @param amoVault The address of the AMO vault to check.
* @return True if the AMO vault is active, false otherwise.
*/
function isAmoActive(address amoVault) public view returns (bool) {
return _amoVaults.contains(amoVault);
}
/**
* @notice Returns the allocation for a specific AMO vault.
* @param amoVault The address of the AMO vault.
* @return The current allocation for the vault.
*/
function amoVaultAllocation(
address amoVault
) public view returns (uint256) {
(bool exists, uint256 allocation) = _amoVaults.tryGet(amoVault);
return exists ? allocation : 0;
}
/**
* @notice Returns the list of all AMO vaults.
* @return The list of AMO vault addresses.
*/
function amoVaults() public view returns (address[] memory) {
return _amoVaults.keys();
}
/**
* @notice Enables an AMO vault.
* @param amoVault The address of the AMO vault.
*/
function enableAmoVault(
address amoVault
) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (_amoVaults.contains(amoVault)) {
revert AmoVaultAlreadyEnabled(amoVault);
}
_amoVaults.set(amoVault, 0);
emit AmoVaultSet(amoVault, true);
}
/**
* @notice Disables an AMO vault.
* @param amoVault The address of the AMO vault.
*/
function disableAmoVault(
address amoVault
) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (!_amoVaults.contains(amoVault)) {
revert InactiveAmoVault(amoVault);
}
_amoVaults.remove(amoVault);
emit AmoVaultSet(amoVault, false);
}
/* Collateral Management */
/**
* @notice Returns the total collateral value of all active AMO vaults.
* @return The total collateral value in USD.
*/
function totalCollateralValue() public view returns (uint256) {
uint256 totalUsdValue = 0;
for (uint256 i = 0; i < _amoVaults.length(); i++) {
(address vaultAddress, ) = _amoVaults.at(i);
if (isAmoActive(vaultAddress)) {
totalUsdValue += AmoVault(vaultAddress).totalCollateralValue();
}
}
return totalUsdValue;
}
/**
* @notice Transfers collateral from an AMO vault to the holding vault.
* @param amoVault The address of the AMO vault.
* @param token The address of the collateral token to transfer.
* @param amount The amount of collateral to transfer.
*/
function transferFromAmoVaultToHoldingVault(
address amoVault,
address token,
uint256 amount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
if (token == address(dusd)) {
revert CannotTransferDUSD();
}
// Update allocation
// A note on why we modify AMO allocation when we withdraw collateral:
// 1. When dUSD AMO enters the AMO vault, the dUSD is initially unbacked
// 2. Over time the AMO vault accrues collateral in exchange for distributing dUSD
// 3. We may be able to make better use of that collateral in a different collateral vault
// 4. So we transfer the collateral out of the AMO vault, but at that point the dUSD that
// converted to that collateral is now free-floating and fully backed
// 5. Thus we decrement the AMO allocation to reflect the fact that the dUSD is no longer
// unbacked, but is actually fully backed and circulating
uint256 collateralUsdValue = collateralHolderVault.assetValueFromAmount(
amount,
token
);
uint256 collateralInDusd = usdValueToDusdAmount(collateralUsdValue);
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
_amoVaults.set(amoVault, currentAllocation - collateralInDusd);
totalAllocated -= collateralInDusd;
// Transfer the collateral
AmoVault(amoVault).withdrawTo(
address(collateralHolderVault),
amount,
token
);
}
/**
* @notice Transfers collateral from the holding vault to an AMO vault.
* @param amoVault The address of the AMO vault.
* @param token The address of the collateral token to transfer.
* @param amount The amount of collateral to transfer.
*/
function transferFromHoldingVaultToAmoVault(
address amoVault,
address token,
uint256 amount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
if (token == address(dusd)) {
revert CannotTransferDUSD();
}
if (!_amoVaults.contains(amoVault)) {
revert InactiveAmoVault(amoVault);
}
// Update allocation
// A note on why we modify AMO allocation when we deposit collateral:
// 1. When we deposit collateral, it can be used to buy back dUSD
// 2. When we buy back dUSD, the dUSD is now unbacked (a redemption)
// 3. Thus any collateral deposited to an AMO vault can create unbacked dUSD,
// which means the AMO allocation for that vault must be increased to reflect this
uint256 collateralUsdValue = collateralHolderVault.assetValueFromAmount(
amount,
token
);
uint256 collateralInDusd = usdValueToDusdAmount(collateralUsdValue);
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
_amoVaults.set(amoVault, currentAllocation + collateralInDusd);
totalAllocated += collateralInDusd;
// Transfer the collateral
collateralHolderVault.withdrawTo(amoVault, amount, token);
}
/* Profit Management */
/**
* @notice Returns the available profit for a specific vault in USD.
* @param vaultAddress The address of the AMO vault to check.
* @return The available profit in USD (can be negative).
*/
function availableVaultProfitsInUsd(
address vaultAddress
) public view returns (int256) {
uint256 totalVaultValueInUsd = AmoVault(vaultAddress).totalValue();
uint256 allocatedDusd = amoVaultAllocation(vaultAddress);
uint256 allocatedValueInUsd = dusdAmountToUsdValue(allocatedDusd);
return int256(totalVaultValueInUsd) - int256(allocatedValueInUsd);
}
/**
* @notice Withdraws profits from an AMO vault to a recipient.
* @param amoVault The AMO vault from which to withdraw profits.
* @param recipient The address to receive the profits.
* @param takeProfitToken The collateral token to withdraw.
* @param takeProfitAmount The amount of collateral to withdraw.
* @return takeProfitValueInUsd The value of the withdrawn profits in USD.
*/
function withdrawProfits(
AmoVault amoVault,
address recipient,
address takeProfitToken,
uint256 takeProfitAmount
)
public
onlyRole(FEE_COLLECTOR_ROLE)
returns (uint256 takeProfitValueInUsd)
{
// Leave open the possibility of withdrawing profits from inactive vaults
takeProfitValueInUsd = amoVault.assetValueFromAmount(
takeProfitAmount,
takeProfitToken
);
int256 _availableProfitInUsd = availableVaultProfitsInUsd(
address(amoVault)
);
// Make sure we are withdrawing less than the available profit
if (
_availableProfitInUsd <= 0 ||
int256(takeProfitValueInUsd) > _availableProfitInUsd
) {
revert InsufficientProfits(
takeProfitValueInUsd,
_availableProfitInUsd
);
}
// Withdraw profits from the vault
amoVault.withdrawTo(recipient, takeProfitAmount, takeProfitToken);
emit ProfitsWithdrawn(address(amoVault), takeProfitValueInUsd);
return takeProfitValueInUsd;
}
/**
* @notice Returns the total available profit across all AMO vaults in USD.
* @return The total available profit in USD.
*/
function availableProfitInUsd() public view returns (int256) {
int256 totalProfit = 0;
// Iterate through all AMO vaults
for (uint256 i = 0; i < _amoVaults.length(); i++) {
(address vaultAddress, ) = _amoVaults.at(i);
if (isAmoActive(vaultAddress)) {
totalProfit += availableVaultProfitsInUsd(vaultAddress);
}
}
return totalProfit;
}
/* Utility */
/**
* @notice Converts a USD value to an equivalent amount of dUSD tokens.
* @param usdValue The amount of USD value to convert.
* @return The equivalent amount of dUSD tokens.
*/
function usdValueToDusdAmount(
uint256 usdValue
) public view returns (uint256) {
uint8 dusdDecimals = dusd.decimals();
return (usdValue * (10 ** dusdDecimals)) / USD_UNIT;
}
/**
* @notice Converts an amount of dUSD tokens to an equivalent USD value.
* @param dusdAmount The amount of dUSD tokens to convert.
* @return The equivalent amount of USD value.
*/
function dusdAmountToUsdValue(
uint256 dusdAmount
) public view returns (uint256) {
uint8 dusdDecimals = dusd.decimals();
return
(dusdAmount * oracle.getAssetPrice(address(dusd))) /
(10 ** dusdDecimals);
}
/* Admin */
/**
* @notice Sets the collateral vault address
* @param _collateralVault The address of the new collateral vault
*/
function setCollateralVault(
address _collateralVault
) external onlyRole(DEFAULT_ADMIN_ROLE) {
collateralHolderVault = CollateralVault(_collateralVault);
}
}
/**
* @title ICollateralSum
* @dev Interface for contracts that can provide total collateral value.
*/
interface ICollateralSum {
/**
* @notice Returns the total collateral value of the implementing contract.
* @return The total collateral value in base value (e.g., USD).
*/
function totalCollateralValue() external view returns (uint256);
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-5/token/ERC20/IERC20.sol";
import "contracts/token/IERC20Stablecoin.sol";
import "contracts/dusd/AmoManager.sol";
import "@openzeppelin/contracts-5/utils/Address.sol";
import "@openzeppelin/contracts-5/utils/ReentrancyGuard.sol";
import "contracts/dusd/CollateralVault.sol";
interface IRecoverable {
function recoverERC20(address token, address to, uint256 amount) external;
function recoverETH(address to, uint256 amount) external;
}
/**
* @title AmoVault
* @notice Base contract for AMO (Algorithmic Market Operations) vaults that manage dUSD and collateral assets
*/
abstract contract AmoVault is CollateralVault, IRecoverable, ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address payable;
/* Core state */
IERC20Stablecoin public immutable dusd;
uint8 public immutable dusdDecimals;
AmoManager public amoManager;
/* Roles */
bytes32 public constant RECOVERER_ROLE = keccak256("RECOVERER_ROLE");
/* Errors */
error CannotRecoverVaultToken(address token);
error InvalidAmoManager();
constructor(
address _dusd,
address _amoManager,
address _admin,
address _collateralWithdrawer,
address _recoverer,
IPriceOracleGetter _oracle
) CollateralVault(_oracle) {
dusd = IERC20Stablecoin(_dusd);
dusdDecimals = IERC20Metadata(_dusd).decimals();
amoManager = AmoManager(_amoManager);
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
grantRole(COLLATERAL_WITHDRAWER_ROLE, _collateralWithdrawer);
grantRole(RECOVERER_ROLE, _recoverer);
approveAmoManager();
}
/**
* @notice Approves the AmoManager to spend dUSD on behalf of this contract
* @dev Only callable by the contract owner or an account with the DEFAULT_ADMIN_ROLE
*/
function approveAmoManager() public onlyRole(DEFAULT_ADMIN_ROLE) {
dusd.approve(address(amoManager), type(uint256).max);
}
/**
* @notice Sets a new AmoManager address
* @param _newAmoManager The address of the new AmoManager
* @dev Only callable by an account with the DEFAULT_ADMIN_ROLE
*/
function setAmoManager(
address _newAmoManager
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_newAmoManager == address(0)) revert InvalidAmoManager();
// Reset allowance for old AMO manager
dusd.approve(address(amoManager), 0);
// Set new AMO manager
amoManager = AmoManager(_newAmoManager);
// Approve new AMO manager
approveAmoManager();
}
/* Recovery */
/**
* @notice Recovers ERC20 tokens accidentally sent to the contract
* @param token The address of the token to recover
* @param to The address to send the tokens to
* @param amount The amount of tokens to recover
*/
function recoverERC20(
address token,
address to,
uint256 amount
) external onlyRole(RECOVERER_ROLE) nonReentrant {
if (token == address(dusd) || isCollateralSupported(token)) {
revert CannotRecoverVaultToken(token);
}
IERC20(token).safeTransfer(to, amount);
}
/**
* @notice Recovers ETH accidentally sent to the contract
* @param to The address to send the ETH to
* @param amount The amount of ETH to recover
*/
function recoverETH(
address to,
uint256 amount
) external onlyRole(RECOVERER_ROLE) {
payable(to).sendValue(amount);
}
/* Virtual functions */
/**
* @notice Calculates the total value of non-dUSD collateral assets in the vault
* @return The total value of collateral assets denominated in the base currency (e.g., USD)
* @dev Must be implemented by derived contracts
*/
function totalCollateralValue() public view virtual returns (uint256);
/**
* @notice Calculates the total value of dUSD holdings in the vault
* @return The total value of dUSD holdings denominated in the base currency (e.g., USD)
* @dev Must be implemented by derived contracts
*/
function totalDusdValue() public view virtual returns (uint256);
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "@openzeppelin/contracts-5/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts-5/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-5/utils/structs/EnumerableSet.sol";
import "contracts/shared/Constants.sol";
import "contracts/lending/core/interfaces/IPriceOracleGetter.sol";
import "contracts/dusd/OracleAware.sol";
/**
* @title CollateralVault
* @notice Abstract contract for any contract that manages collateral assets
\ */
abstract contract CollateralVault is AccessControl, OracleAware {
using SafeERC20 for IERC20Metadata;
using EnumerableSet for EnumerableSet.AddressSet;
/* Core state */
EnumerableSet.AddressSet internal _supportedCollaterals;
/* Events */
event CollateralAllowed(address indexed collateralAsset);
event CollateralDisallowed(address indexed collateralAsset);
/* Roles */
bytes32 public constant COLLATERAL_MANAGER_ROLE =
keccak256("COLLATERAL_MANAGER_ROLE");
bytes32 public constant COLLATERAL_STRATEGY_ROLE =
keccak256("COLLATERAL_STRATEGY_ROLE");
bytes32 public constant COLLATERAL_WITHDRAWER_ROLE =
keccak256("COLLATERAL_WITHDRAWER_ROLE");
/* Errors */
error UnsupportedCollateral(address collateralAsset);
error CollateralAlreadyAllowed(address collateralAsset);
error NoOracleSupport(address collateralAsset);
error FailedToAddCollateral(address collateralAsset);
error CollateralNotSupported(address collateralAsset);
error MustSupportAtLeastOneCollateral();
error FailedToRemoveCollateral(address collateralAsset);
/**
* @notice Initializes the vault with an oracle and sets up initial roles
* @dev Grants all roles to the contract deployer initially
* @param oracle The price oracle to use for collateral valuation
*/
constructor(
IPriceOracleGetter oracle
) OracleAware(oracle, Constants.ORACLE_BASE_CURRENCY_UNIT) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender); // This is the super admin
grantRole(COLLATERAL_MANAGER_ROLE, msg.sender);
grantRole(COLLATERAL_WITHDRAWER_ROLE, msg.sender);
grantRole(COLLATERAL_STRATEGY_ROLE, msg.sender);
}
/* Deposit */
/**
* @notice Deposit collateral into the vault
* @param collateralAmount The amount of collateral to deposit
* @param collateralAsset The address of the collateral asset
*/
function deposit(uint256 collateralAmount, address collateralAsset) public {
if (!_supportedCollaterals.contains(collateralAsset)) {
revert UnsupportedCollateral(collateralAsset);
}
IERC20Metadata(collateralAsset).safeTransferFrom(
msg.sender,
address(this),
collateralAmount
);
}
/* Withdrawal */
/**
* @notice Withdraws collateral from the vault
* @param collateralAmount The amount of collateral to withdraw
* @param collateralAsset The address of the collateral asset
*/
function withdraw(
uint256 collateralAmount,
address collateralAsset
) public onlyRole(COLLATERAL_WITHDRAWER_ROLE) {
return _withdraw(msg.sender, collateralAmount, collateralAsset);
}
/**
* @notice Withdraws collateral from the vault to a specific address
* @param recipient The address receiving the collateral
* @param collateralAmount The amount of collateral to withdraw
* @param collateralAsset The address of the collateral asset
*/
function withdrawTo(
address recipient,
uint256 collateralAmount,
address collateralAsset
) public onlyRole(COLLATERAL_WITHDRAWER_ROLE) {
return _withdraw(recipient, collateralAmount, collateralAsset);
}
/**
* @notice Internal function to withdraw collateral from the vault
* @param withdrawer The address withdrawing the collateral
* @param collateralAmount The amount of collateral to withdraw
* @param collateralAsset The address of the collateral asset
*/
function _withdraw(
address withdrawer,
uint256 collateralAmount,
address collateralAsset
) internal {
IERC20Metadata(collateralAsset).safeTransfer(
withdrawer,
collateralAmount
);
}
/* Collateral Info */
/**
* @notice Calculates the total value of all assets in the vault
* @return usdValue The total value of all assets in USD
*/
function totalValue() public view virtual returns (uint256 usdValue);
/**
* @notice Calculates the USD value of a given amount of an asset
* @param assetAmount The amount of the asset
* @param asset The address of the asset
* @return usdValue The USD value of the asset
*/
function assetValueFromAmount(
uint256 assetAmount,
address asset
) public view returns (uint256 usdValue) {
uint256 assetPrice = oracle.getAssetPrice(asset);
uint8 assetDecimals = IERC20Metadata(asset).decimals();
return (assetPrice * assetAmount) / (10 ** assetDecimals);
}
/**
* @notice Calculates the amount of an asset that corresponds to a given USD value
* @param usdValue The USD value
* @param asset The address of the asset
* @return assetAmount The amount of the asset
*/
function assetAmountFromValue(
uint256 usdValue,
address asset
) public view returns (uint256 assetAmount) {
uint256 assetPrice = oracle.getAssetPrice(asset);
uint8 assetDecimals = IERC20Metadata(asset).decimals();
return (usdValue * (10 ** assetDecimals)) / assetPrice;
}
/* Collateral management */
/**
* @notice Allows a new collateral asset
* @param collateralAsset The address of the collateral asset
*/
function allowCollateral(
address collateralAsset
) public onlyRole(COLLATERAL_MANAGER_ROLE) {
if (_supportedCollaterals.contains(collateralAsset)) {
revert CollateralAlreadyAllowed(collateralAsset);
}
if (oracle.getAssetPrice(collateralAsset) == 0) {
revert NoOracleSupport(collateralAsset);
}
if (!_supportedCollaterals.add(collateralAsset)) {
revert FailedToAddCollateral(collateralAsset);
}
emit CollateralAllowed(collateralAsset);
}
/**
* @notice Disallows a previously supported collateral asset
* @dev Requires at least one collateral asset to remain supported
* @param collateralAsset The address of the collateral asset to disallow
*/
function disallowCollateral(
address collateralAsset
) public onlyRole(COLLATERAL_MANAGER_ROLE) {
if (!_supportedCollaterals.contains(collateralAsset)) {
revert CollateralNotSupported(collateralAsset);
}
if (_supportedCollaterals.length() <= 1) {
revert MustSupportAtLeastOneCollateral();
}
if (!_supportedCollaterals.remove(collateralAsset)) {
revert FailedToRemoveCollateral(collateralAsset);
}
emit CollateralDisallowed(collateralAsset);
}
/**
* @notice Checks if a given asset is supported as collateral
* @param collateralAsset The address of the collateral asset to check
* @return bool True if the asset is supported, false otherwise
*/
function isCollateralSupported(
address collateralAsset
) public view returns (bool) {
return _supportedCollaterals.contains(collateralAsset);
}
/**
* @notice Returns a list of all supported collateral assets
* @return address[] Array of collateral asset addresses
*/
function listCollateral() public view returns (address[] memory) {
return _supportedCollaterals.values();
}
/**
* @notice Calculates the total USD value of all supported collateral assets in the vault
* @dev Iterates through all supported collaterals and sums their USD values
* @return uint256 The total value in USD
*/
function _totalValueOfSupportedCollaterals()
internal
view
returns (uint256)
{
uint256 totalUsdValue = 0;
for (uint256 i = 0; i < _supportedCollaterals.length(); i++) {
address collateral = _supportedCollaterals.at(i);
uint256 collateralPrice = oracle.getAssetPrice(collateral);
uint8 collateralDecimals = IERC20Metadata(collateral).decimals();
uint256 collateralValue = (collateralPrice *
IERC20Metadata(collateral).balanceOf(address(this))) /
(10 ** collateralDecimals);
totalUsdValue += collateralValue;
}
return totalUsdValue;
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "contracts/lending/core/interfaces/IPriceOracleGetter.sol";
/**
* @title OracleAware
* @notice Abstract contract that provides oracle functionality to other contracts
*/
abstract contract OracleAware is AccessControl {
/* Core state */
IPriceOracleGetter public oracle;
uint256 public baseCurrencyUnit;
/* Events */
event OracleSet(address indexed newOracle);
/* Errors */
error IncorrectBaseCurrencyUnit(uint256 baseCurrencyUnit);
/**
* @notice Initializes the contract with an oracle and base currency unit
* @param initialOracle The initial oracle to use for price feeds
* @param _baseCurrencyUnit The base currency unit for price calculations
* @dev Sets up the initial oracle and base currency unit values
*/
constructor(IPriceOracleGetter initialOracle, uint256 _baseCurrencyUnit) {
oracle = initialOracle;
baseCurrencyUnit = _baseCurrencyUnit;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
/**
* @notice Sets the oracle to use for collateral valuation
* @param newOracle The new oracle to use
*/
function setOracle(
IPriceOracleGetter newOracle
) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (newOracle.BASE_CURRENCY_UNIT() != baseCurrencyUnit) {
revert IncorrectBaseCurrencyUnit(baseCurrencyUnit);
}
oracle = newOracle;
emit OracleSet(address(newOracle));
}
/**
* @notice Updates the base currency unit used for price calculations
* @param _newBaseCurrencyUnit The new base currency unit to set
* @dev Only used if the oracle's base currency unit changes
*/
function setBaseCurrencyUnit(
uint256 _newBaseCurrencyUnit
) public onlyRole(DEFAULT_ADMIN_ROLE) {
baseCurrencyUnit = _newBaseCurrencyUnit;
}
}// SPDX-License-Identifier: AGPL-3.0
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.0;
import {IPriceOracleGetter} from "./IPriceOracleGetter.sol";
import {IPoolAddressesProvider} from "./IPoolAddressesProvider.sol";
/**
* @title IAaveOracle
* @author Aave
* @notice Defines the basic interface for the Aave Oracle
*/
interface IAaveOracle is IPriceOracleGetter {
/**
* @dev Emitted after the base currency is set
* @param baseCurrency The base currency of used for price quotes
* @param baseCurrencyUnit The unit of the base currency
*/
event BaseCurrencySet(
address indexed baseCurrency,
uint256 baseCurrencyUnit
);
/**
* @dev Emitted after the price source of an asset is updated
* @param asset The address of the asset
* @param source The price source of the asset
*/
event AssetSourceUpdated(address indexed asset, address indexed source);
/**
* @dev Emitted after the address of fallback oracle is updated
* @param fallbackOracle The address of the fallback oracle
*/
event FallbackOracleUpdated(address indexed fallbackOracle);
/**
* @notice Returns the PoolAddressesProvider
* @return The address of the PoolAddressesProvider contract
*/
function ADDRESSES_PROVIDER()
external
view
returns (IPoolAddressesProvider);
/**
* @notice Sets or replaces price sources of assets
* @param assets The addresses of the assets
* @param sources The addresses of the price sources
*/
function setAssetSources(
address[] calldata assets,
address[] calldata sources
) external;
/**
* @notice Sets the fallback oracle
* @param fallbackOracle The address of the fallback oracle
*/
function setFallbackOracle(address fallbackOracle) external;
/**
* @notice Returns a list of prices from a list of assets addresses
* @param assets The list of assets addresses
* @return The prices of the given assets
*/
function getAssetsPrices(
address[] calldata assets
) external view returns (uint256[] memory);
/**
* @notice Returns the address of the source for an asset address
* @param asset The address of the asset
* @return The address of the source
*/
function getSourceOfAsset(address asset) external view returns (address);
/**
* @notice Returns the address of the fallback oracle
* @return The address of the fallback oracle
*/
function getFallbackOracle() external view returns (address);
}// SPDX-License-Identifier: AGPL-3.0
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.0;
/**
* @title IPoolAddressesProvider
* @author Aave
* @notice Defines the basic interface for a Pool Addresses Provider.
*/
interface IPoolAddressesProvider {
/**
* @dev Emitted when the market identifier is updated.
* @param oldMarketId The old id of the market
* @param newMarketId The new id of the market
*/
event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);
/**
* @dev Emitted when the pool is updated.
* @param oldAddress The old address of the Pool
* @param newAddress The new address of the Pool
*/
event PoolUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the pool configurator is updated.
* @param oldAddress The old address of the PoolConfigurator
* @param newAddress The new address of the PoolConfigurator
*/
event PoolConfiguratorUpdated(
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when the price oracle is updated.
* @param oldAddress The old address of the PriceOracle
* @param newAddress The new address of the PriceOracle
*/
event PriceOracleUpdated(
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when the ACL manager is updated.
* @param oldAddress The old address of the ACLManager
* @param newAddress The new address of the ACLManager
*/
event ACLManagerUpdated(
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when the ACL admin is updated.
* @param oldAddress The old address of the ACLAdmin
* @param newAddress The new address of the ACLAdmin
*/
event ACLAdminUpdated(
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when the price oracle sentinel is updated.
* @param oldAddress The old address of the PriceOracleSentinel
* @param newAddress The new address of the PriceOracleSentinel
*/
event PriceOracleSentinelUpdated(
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when the pool data provider is updated.
* @param oldAddress The old address of the PoolDataProvider
* @param newAddress The new address of the PoolDataProvider
*/
event PoolDataProviderUpdated(
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when a new proxy is created.
* @param id The identifier of the proxy
* @param proxyAddress The address of the created proxy contract
* @param implementationAddress The address of the implementation contract
*/
event ProxyCreated(
bytes32 indexed id,
address indexed proxyAddress,
address indexed implementationAddress
);
/**
* @dev Emitted when a new non-proxied contract address is registered.
* @param id The identifier of the contract
* @param oldAddress The address of the old contract
* @param newAddress The address of the new contract
*/
event AddressSet(
bytes32 indexed id,
address indexed oldAddress,
address indexed newAddress
);
/**
* @dev Emitted when the implementation of the proxy registered with id is updated
* @param id The identifier of the contract
* @param proxyAddress The address of the proxy contract
* @param oldImplementationAddress The address of the old implementation contract
* @param newImplementationAddress The address of the new implementation contract
*/
event AddressSetAsProxy(
bytes32 indexed id,
address indexed proxyAddress,
address oldImplementationAddress,
address indexed newImplementationAddress
);
/**
* @notice Returns the id of the Aave market to which this contract points to.
* @return The market id
*/
function getMarketId() external view returns (string memory);
/**
* @notice Associates an id with a specific PoolAddressesProvider.
* @dev This can be used to create an onchain registry of PoolAddressesProviders to
* identify and validate multiple Aave markets.
* @param newMarketId The market id
*/
function setMarketId(string calldata newMarketId) external;
/**
* @notice Returns an address by its identifier.
* @dev The returned address might be an EOA or a contract, potentially proxied
* @dev It returns ZERO if there is no registered address with the given id
* @param id The id
* @return The address of the registered for the specified id
*/
function getAddressFromID(bytes32 id) external view returns (address);
/**
* @notice General function to update the implementation of a proxy registered with
* certain `id`. If there is no proxy registered, it will instantiate one and
* set as implementation the `newImplementationAddress`.
* @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit
* setter function, in order to avoid unexpected consequences
* @param id The id
* @param newImplementationAddress The address of the new implementation
*/
function setAddressAsProxy(
bytes32 id,
address newImplementationAddress
) external;
/**
* @notice Sets an address for an id replacing the address saved in the addresses map.
* @dev IMPORTANT Use this function carefully, as it will do a hard replacement
* @param id The id
* @param newAddress The address to set
*/
function setAddress(bytes32 id, address newAddress) external;
/**
* @notice Returns the address of the Pool proxy.
* @return The Pool proxy address
*/
function getPool() external view returns (address);
/**
* @notice Updates the implementation of the Pool, or creates a proxy
* setting the new `pool` implementation when the function is called for the first time.
* @param newPoolImpl The new Pool implementation
*/
function setPoolImpl(address newPoolImpl) external;
/**
* @notice Returns the address of the PoolConfigurator proxy.
* @return The PoolConfigurator proxy address
*/
function getPoolConfigurator() external view returns (address);
/**
* @notice Updates the implementation of the PoolConfigurator, or creates a proxy
* setting the new `PoolConfigurator` implementation when the function is called for the first time.
* @param newPoolConfiguratorImpl The new PoolConfigurator implementation
*/
function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;
/**
* @notice Returns the address of the price oracle.
* @return The address of the PriceOracle
*/
function getPriceOracle() external view returns (address);
/**
* @notice Updates the address of the price oracle.
* @param newPriceOracle The address of the new PriceOracle
*/
function setPriceOracle(address newPriceOracle) external;
/**
* @notice Returns the address of the ACL manager.
* @return The address of the ACLManager
*/
function getACLManager() external view returns (address);
/**
* @notice Updates the address of the ACL manager.
* @param newAclManager The address of the new ACLManager
*/
function setACLManager(address newAclManager) external;
/**
* @notice Returns the address of the ACL admin.
* @return The address of the ACL admin
*/
function getACLAdmin() external view returns (address);
/**
* @notice Updates the address of the ACL admin.
* @param newAclAdmin The address of the new ACL admin
*/
function setACLAdmin(address newAclAdmin) external;
/**
* @notice Returns the address of the price oracle sentinel.
* @return The address of the PriceOracleSentinel
*/
function getPriceOracleSentinel() external view returns (address);
/**
* @notice Updates the address of the price oracle sentinel.
* @param newPriceOracleSentinel The address of the new PriceOracleSentinel
*/
function setPriceOracleSentinel(address newPriceOracleSentinel) external;
/**
* @notice Returns the address of the data provider.
* @return The address of the DataProvider
*/
function getPoolDataProvider() external view returns (address);
/**
* @notice Updates the address of the data provider.
* @param newDataProvider The address of the new DataProvider
*/
function setPoolDataProvider(address newDataProvider) external;
}// SPDX-License-Identifier: AGPL-3.0
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.0;
/**
* @title IPriceOracleGetter
* @author Aave
* @notice Interface for the Aave price oracle.
*/
interface IPriceOracleGetter {
/**
* @notice Returns the base currency address
* @dev Address 0x0 is reserved for USD as base currency.
* @return Returns the base currency address.
*/
function BASE_CURRENCY() external view returns (address);
/**
* @notice Returns the base currency unit
* @dev 1 ether for ETH, 1e8 for USD.
* @return Returns the base currency unit.
*/
function BASE_CURRENCY_UNIT() external view returns (uint256);
/**
* @notice Returns the asset price in the base currency
* @param asset The address of the asset
* @return The price of the asset
*/
function getAssetPrice(address asset) external view returns (uint256);
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.0;
library Constants {
// Shared definitions of how we represent percentages and basis points
uint16 public constant ONE_BPS = 100; // 1 basis point with 2 decimals
uint32 public constant ONE_PERCENT_BPS = ONE_BPS * 100;
uint32 public constant ONE_HUNDRED_PERCENT_BPS = ONE_PERCENT_BPS * 100;
uint32 public constant ORACLE_BASE_CURRENCY_UNIT = 1e8;
}// SPDX-License-Identifier: Unlicense
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/token/ERC20/IERC20.sol";
interface IERC20Stablecoin is IERC20 {
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
function burnFrom(address account, uint256 amount) external;
function decimals() external view returns (uint8);
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_collateralVault","type":"address"},{"internalType":"address","name":"_dusd","type":"address"},{"internalType":"contract IPriceOracleGetter","name":"oracle","type":"address"},{"internalType":"address","name":"_amoManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"AssetMintingPaused","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"baseCurrencyUnit","type":"uint256"}],"name":"IncorrectBaseCurrencyUnit","type":"error"},{"inputs":[{"internalType":"uint256","name":"collateralInDusd","type":"uint256"},{"internalType":"uint256","name":"circulatingDusd","type":"uint256"}],"name":"IssuanceSurpassesExcessCollateral","type":"error"},{"inputs":[],"name":"MathOverflowedMulDiv","type":"error"},{"inputs":[{"internalType":"uint256","name":"circulatingDusdBefore","type":"uint256"},{"internalType":"uint256","name":"circulatingDusdAfter","type":"uint256"}],"name":"MintingToAmoShouldNotIncreaseSupply","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"uint256","name":"minDUsd","type":"uint256"},{"internalType":"uint256","name":"dusdAmount","type":"uint256"}],"name":"SlippageTooHigh","type":"error"},{"inputs":[{"internalType":"address","name":"collateralAsset","type":"address"}],"name":"UnsupportedCollateral","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"amoManager","type":"address"}],"name":"AmoManagerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"bool","name":"paused","type":"bool"}],"name":"AssetMintingPauseUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralVault","type":"address"}],"name":"CollateralVaultSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"AMO_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INCENTIVES_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"amoManager","outputs":[{"internalType":"contract AmoManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetMintingPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseCurrencyUnit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"baseValue","type":"uint256"}],"name":"baseValueToDusdAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"circulatingDusd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralInDusd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralVault","outputs":[{"internalType":"contract CollateralVault","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dusd","outputs":[{"internalType":"contract IERC20Stablecoin","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dusdDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"dusdAmount","type":"uint256"}],"name":"increaseAmoSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"isAssetMintingEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"address","name":"collateralAsset","type":"address"},{"internalType":"uint256","name":"minDUsd","type":"uint256"}],"name":"issue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"dusdAmount","type":"uint256"}],"name":"issueUsingExcessCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"oracle","outputs":[{"internalType":"contract IPriceOracleGetter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseMinting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_amoManager","type":"address"}],"name":"setAmoManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"bool","name":"paused","type":"bool"}],"name":"setAssetMintingPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newBaseCurrencyUnit","type":"uint256"}],"name":"setBaseCurrencyUnit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralVault","type":"address"}],"name":"setCollateralVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IPriceOracleGetter","name":"newOracle","type":"address"}],"name":"setOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpauseMinting","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
60a0604090808252346200031a5760808162001d28803803809162000025828562000332565b8339810103126200031a576200003b816200036c565b906020906200004c8282016200036c565b818501516001600160a01b038082169390918490036200031a5760606200007491016200036c565b8651638c89b64f60e01b815260049686828981895afa94851562000327578792600096620002e9575b509084929160018060a01b031996600198888a5416178955600255620000c33362000381565b5060038890558954600580548916938616939093179092556001600160a81b0319909116600891821b610100600160a81b031617808a558a5163313ce56760e01b81529384928b928492911c165afa908115620002de5760009162000299575b50608052169060065416176006556200013c3362000381565b507fbc4aa291e35c786eceb374be398429586e466107044903c4dff7a8687a5300706000526000825280846000200154806000528460002033600052835260ff856000205416156200027b5750620001943362000401565b507f5e1c59076ed40a2f2d7578e32a78795e357500600174cc5627a8953d155117226000526000825280846000200154806000528460002033600052835260ff856000205416156200027b5750620001ec33620004a3565b5060008051602062001d088339815191526000526000825283600020015490816000528360002090336000525260ff83600020541615620002605782620002333362000540565b505161171b9081620005cd823960805181818161082e0152818161087101528181610a1d015261123c0152f35b604492519163e2517d3f60e01b835233908301526024820152fd5b845163e2517d3f60e01b815233818601526024810191909152604490fd5b8681813d8311620002d6575b620002b1818362000332565b81010312620002d257519060ff82168203620002cf57503862000123565b80fd5b5080fd5b503d620002a5565b88513d6000823e3d90fd5b838193949297503d83116200031f575b62000305818362000332565b810103126200031a575193869190846200009d565b600080fd5b503d620002f9565b89513d6000823e3d90fd5b601f909101601f19168101906001600160401b038211908210176200035657604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200031a57565b6001600160a01b031660008181527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604081205490919060ff16620003fd57818052816020526040822081835260205260408220600160ff19825416179055339160008051602062001ce88339815191528180a4600190565b5090565b6001600160a01b031660008181527fb4e978eb1f17c73554a1f8756605f820ff335cf1d951e33a14aae148be785b3560205260408120549091907fbc4aa291e35c786eceb374be398429586e466107044903c4dff7a8687a5300709060ff166200049e57808352826020526040832082845260205260408320600160ff1982541617905560008051602062001ce8833981519152339380a4600190565b505090565b6001600160a01b031660008181527f208ae9e64e9ba55a56e2d6d47e04a023559b118ccb25802ecbf309139c5ef96c60205260408120549091907f5e1c59076ed40a2f2d7578e32a78795e357500600174cc5627a8953d155117229060ff166200049e57808352826020526040832082845260205260408320600160ff1982541617905560008051602062001ce8833981519152339380a4600190565b6001600160a01b031660008181527ff7c9542c591017a21c74b6f3fab6263c7952fc0aaf9db4c22a2a04ddc7f8674f602052604081205490919060008051602062001d088339815191529060ff166200049e57808352826020526040832082845260205260408320600160ff1982541617905560008051602062001ce8833981519152339380a460019056fe608060408181526004918236101561001657600080fd5b600092833560e01c91826301ffc9a714610f96575081630bece79c14610f6d578163248a9ca314610f435781632abcdf6814610e2c5781632f2ff15d14610e0257816336568abe14610dbc578163369ecbd514610cc45781633ffa884c1461091c578163426c0ab8146108ff57816344eab154146108c15781634a0bbabb1461089f5781634ac5410a1461085257816352816b68146108145781635b8862dc146107eb5781635c975abb146107c857816368a806c41461075d5781637adbf973146106675781637dc0d1d01461063e57816391d14854146105f95781639866f320146105be5781639a0e29371461058f5781639d4cc8d3146104ab578163a217fddf14610490578163ae20032214610429578163b22fb67d14610405578163d547741f146103c6578163da8fbf2a1461036c578163de1409ce14610340578163e63ab1e914610305578163e797c589146101ce57508063f3bddde1146101b05763f817f7d01461018557600080fd5b346101ac57816003193601126101ac5760065490516001600160a01b039091168152602090f35b5080fd5b50346101ac57816003193601126101ac576020906002549051908152f35b919050346102d957806003193601126102d9576101e9610fff565b7f5e1c59076ed40a2f2d7578e32a78795e357500600174cc5627a8953d15511722806000526000602052826000203360005260205260ff836000205416156102e757508390610236611371565b835460081c6001600160a01b0316803b156102d95783516340c10f1960e01b81526001600160a01b0390921685830190815260243560208201528391839182908490829060400103925af180156102dd576102c5575b50506102966110a0565b9061029f6111fe565b908282106102ab578480f35b516379f1a2a760e01b815292830152602482015260449150fd5b6102ce90611015565b6102d957823861028c565b8280fd5b83513d84823e3d90fd5b825163e2517d3f60e01b815233818601526024810191909152604490fd5b5050346101ac57816003193601126101ac57602090517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b9050346102d957826003193601126102d95754905160089190911c6001600160a01b0316815260209150f35b9050346102d957826003193601126102d9577f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258916020916103ab611297565b6103b3611371565b805460ff1916600117905551338152a180f35b919050346102d957806003193601126102d95761040191356103fc60016103eb610fe9565b93838752866020528620015461134b565b611541565b5080f35b5050346101ac57816003193601126101ac576020906104226111fe565b9051908152f35b9050346102d957826003193601126102d957610443611297565b805460ff8116156104815760ff19169055513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa90602090a180f35b509051638dfc202b60e01b8152fd5b5050346101ac57816003193601126101ac5751908152602090f35b5050346101ac5760203660031901126101ac576104c6610fff565b6104ce611311565b6006549160018060a01b039081841691831680946bffffffffffffffffffffffff60a01b16176006556105256000805160206116c683398151915293848752866020526105206001848920015461134b565b61138f565b5081151580610585575b61055e575b5050507fc1ed2a2e76938d5535836b1406331936bd2440c622b1314c8459f98a5065577b8280a280f35b60016105779161057c948752866020528620015461134b565b6114a8565b50388080610534565b508382141561052f565b5050346101ac5760203660031901126101ac576020906105b56105b0610fff565b61117d565b90519015158152f35b5050346101ac57816003193601126101ac57602090517f5e1c59076ed40a2f2d7578e32a78795e357500600174cc5627a8953d155117228152f35b9050346102d957816003193601126102d95781602093610617610fe9565b92358152808552209060018060a01b0316600052825260ff81600020541690519015158152f35b5050346101ac57816003193601126101ac5760015490516001600160a01b039091168152602090f35b9050346102d95760203660031901126102d95780356001600160a01b038116929083900361075957610697611311565b8051638c89b64f60e01b8152906020828481875afa91821561074f578592610716575b50600254809203610701575050600180546001600160a01b03191683179055507f3f32684a32a11dabdbb8c0177de80aa3ae36a004d75210335b49e544e48cd0aa8280a280f35b51639b6812b960e01b81529182015260249150fd5b9091506020813d602011610747575b816107326020938361103f565b81010312610742575190386106ba565b600080fd5b3d9150610725565b81513d87823e3d90fd5b8380fd5b83346107c55760203660031901126107c557610777610fff565b61077f611311565b600580546001600160a01b0319166001600160a01b039290921691821790557fc92ec24b34ad9d3aa14cd5be87b888d7790d40903da5f44c6367b2fb6cdb20838280a280f35b80fd5b8284346107c557806003193601126107c5575060ff602092541690519015158152f35b5050346101ac57816003193601126101ac57602090516000805160206116c68339815191528152f35b5050346101ac57816003193601126101ac576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b8284346107c55760203660031901126107c557506104226020926108957f0000000000000000000000000000000000000000000000000000000000000000611079565b60025491356115b6565b8390346101ac5760203660031901126101ac576108ba611311565b3560025580f35b5050346101ac5760203660031901126101ac5760209160ff9082906001600160a01b036108ec610fff565b1681526007855220541690519015158152f35b5050346101ac57816003193601126101ac576020906104226110a0565b8383346101ac5760603660031901126101ac5782359061093a610fe9565b9360443592600260035414610cb5576002600355610956611371565b6005548351637d35e97760e11b81526001600160a01b039788168482018190526024989390926020929085169083818c81855afa908115610c53578a91610c88575b5015610c72578389526007835260ff878a205416610c5d57865163313ce56760e01b815283818881885afa908115610c53578a91610c18575b508a8487600154168a519283809263b3596f0760e01b82528a8d8301525afa908115610c0e5784908c92610bdc575b5090610a1891610a12610a4a94611079565b916115b6565b610a417f0000000000000000000000000000000000000000000000000000000000000000611079565b600254916115b6565b97808910610bc15750865191838301916323b872dd60e01b8352338c850152604484015260648301526064825260a082019167ffffffffffffffff9281811084821117610bad57885251899182919082875af13d15610ba0573d918211610b8e5790610ad591875191610ac685601f19601f840116018461103f565b82523d8a8584013e5b84611662565b8051918215159283610b75575b505050610b605750849550815460081c1692833b15610b5c5782516340c10f1960e01b815233928101928352602083019190915292849184919082908490829060400103925af1908115610b535750610b3f575b50600160035580f35b610b4890611015565b6107c5578082610b36565b513d84823e3d90fd5b8480fd5b905085925191635274afe760e01b8352820152fd5b610b859350820181019101611061565b15888080610ae2565b634e487b7160e01b8952604186528989fd5b610ad59150606090610acf565b8b604189634e487b7160e01b600052526000fd5b86898c6044938b5193633b5d56ed60e11b8552840152820152fd5b809250868092503d8311610c07575b610bf5818361103f565b81010312610742575183610a4a610a00565b503d610beb565b89513d8d823e3d90fd5b90508381813d8311610c4c575b610c2f818361103f565b81010312610c48575160ff81168103610c48578b6109d1565b8980fd5b503d610c25565b88513d8c823e3d90fd5b86516260f4e760e01b81528087018590528a90fd5b8651632762993f60e11b81528087018590528a90fd5b610ca89150843d8611610cae575b610ca0818361103f565b810190611061565b8b610998565b503d610c96565b509051633ee5aeb560e01b8152fd5b9050346102d957816003193601126102d957610cde610fff565b9160243591821515809303610b5c57610cf5611297565b6005548251637d35e97760e11b81526001600160a01b0395861683820181905295909160209183916024918391165afa908115610db2578691610d93575b5015610d7e57507f6a3f78a37af68267ecb1a43ebdb9a1efb82ceb0f25ab9d945d85ce216406d87d916020918486526007835280862060ff1981541660ff841617905551908152a280f35b836024925191632762993f60e11b8352820152fd5b610dac915060203d602011610cae57610ca0818361103f565b38610d33565b83513d88823e3d90fd5b8383346101ac57806003193601126101ac57610dd6610fe9565b90336001600160a01b03831603610df35750610401919235611541565b5163334bd91960e11b81528390fd5b919050346102d957806003193601126102d9576104019135610e2760016103eb610fe9565b61142f565b919050346102d95760203660031901126102d9576000805160206116c68339815191528084528360205281842033855260205260ff828520541615610f275750610e74611371565b610e7c6110a0565b8254600654859160081c6001600160a01b039081169116813b156102d95784516340c10f1960e01b81526001600160a01b0390911686820190815286356020820152909291839182908490829060400103925af18015610f1d57610f0a575b50610ee46110a0565b91818303610ef0578480f35b5163353d06cf60e21b815292830152602482015260449150fd5b610f1690949194611015565b9238610edb565b83513d87823e3d90fd5b60449291519163e2517d3f60e01b835233908301526024820152fd5b9050346102d95760203660031901126102d957816020936001923581528085522001549051908152f35b5050346101ac57816003193601126101ac5760055490516001600160a01b039091168152602090f35b8491346102d95760203660031901126102d9573563ffffffff60e01b81168091036102d95760209250637965db0b60e01b8114908115610fd8575b5015158152f35b6301ffc9a760e01b14905083610fd1565b602435906001600160a01b038216820361074257565b600435906001600160a01b038216820361074257565b67ffffffffffffffff811161102957604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff82111761102957604052565b90816020910312610742575180151581036107425790565b60ff16604d811161108a57600a0a90565b634e487b7160e01b600052601160045260246000fd5b600480546040516318160ddd60e01b8152916020916001600160a01b03918391859190829060081c85165afa9283156111425760009361114e575b5081600491600654166040519283809263773df15160e01b82525afa91821561114257600092611114575b5050810390811161108a5790565b90809250813d831161113b575b61112b818361103f565b8101031261074257513880611106565b503d611121565b6040513d6000823e3d90fd5b9092508181813d8311611176575b611166818361103f565b81010312610742575191816110db565b503d61115c565b600554604051637d35e97760e11b81526001600160a01b039283166004820181905292909160209183916024918391165afa908115611142576000916111df575b50156111d957600052600760205260ff604060002054161590565b50600090565b6111f8915060203d602011610cae57610ca0818361103f565b386111be565b6005546040516306a61f7560e51b815290602090829060049082906001600160a01b03165afa801561114257600090611263575b6112609150610a417f0000000000000000000000000000000000000000000000000000000000000000611079565b90565b506020813d60201161128f575b8161127d6020938361103f565b81010312610742576112609051611232565b3d9150611270565b3360009081527ff7c9542c591017a21c74b6f3fab6263c7952fc0aaf9db4c22a2a04ddc7f8674f60205260409020547f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9060ff16156112f35750565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b3360009081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604081205460ff16156112f35750565b80600052600060205260406000203360005260205260ff60406000205416156112f35750565b60ff6004541661137d57565b60405163d93c066560e01b8152600490fd5b6001600160a01b031660008181527fb4e978eb1f17c73554a1f8756605f820ff335cf1d951e33a14aae148be785b3560205260408120549091906000805160206116c68339815191529060ff1661142a57808352826020526040832082845260205260408320600160ff198254161790557f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d339380a4600190565b505090565b9060009180835282602052604083209160018060a01b03169182845260205260ff6040842054161560001461142a57808352826020526040832082845260205260408320600160ff198254161790557f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d339380a4600190565b6001600160a01b031660008181527fb4e978eb1f17c73554a1f8756605f820ff335cf1d951e33a14aae148be785b3560205260408120549091906000805160206116c68339815191529060ff161561142a5780835282602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4600190565b9060009180835282602052604083209160018060a01b03169182845260205260ff60408420541660001461142a5780835282602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4600190565b9091828202916000198482099383808610950394808603951461163e578483111561162c5782910981600003821680920460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029360018380600003040190848311900302920304170290565b60405163227bc15360e01b8152600490fd5b50508092501561164c570490565b634e487b7160e01b600052601260045260246000fd5b90611689575080511561167757805190602001fd5b604051630a12f52160e11b8152600490fd5b815115806116bc575b61169a575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b1561169256febc4aa291e35c786eceb374be398429586e466107044903c4dff7a8687a530070a264697066735822122082301cf235dac7325e1d9210797e5e9fe0e47d36969d6878a922c84cb61ac98064736f6c634300081800332f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a000000000000000000000000624e12de7a97b8cfc1ad1f050a1c9263b1f4febc000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a000000000000000000000000fa7560956807d95dcef22990ddd92e38dbaf5cdd00000000000000000000000049a0c8030ca199f6f246517ae689e3cc0775271a
Deployed Bytecode
0x608060408181526004918236101561001657600080fd5b600092833560e01c91826301ffc9a714610f96575081630bece79c14610f6d578163248a9ca314610f435781632abcdf6814610e2c5781632f2ff15d14610e0257816336568abe14610dbc578163369ecbd514610cc45781633ffa884c1461091c578163426c0ab8146108ff57816344eab154146108c15781634a0bbabb1461089f5781634ac5410a1461085257816352816b68146108145781635b8862dc146107eb5781635c975abb146107c857816368a806c41461075d5781637adbf973146106675781637dc0d1d01461063e57816391d14854146105f95781639866f320146105be5781639a0e29371461058f5781639d4cc8d3146104ab578163a217fddf14610490578163ae20032214610429578163b22fb67d14610405578163d547741f146103c6578163da8fbf2a1461036c578163de1409ce14610340578163e63ab1e914610305578163e797c589146101ce57508063f3bddde1146101b05763f817f7d01461018557600080fd5b346101ac57816003193601126101ac5760065490516001600160a01b039091168152602090f35b5080fd5b50346101ac57816003193601126101ac576020906002549051908152f35b919050346102d957806003193601126102d9576101e9610fff565b7f5e1c59076ed40a2f2d7578e32a78795e357500600174cc5627a8953d15511722806000526000602052826000203360005260205260ff836000205416156102e757508390610236611371565b835460081c6001600160a01b0316803b156102d95783516340c10f1960e01b81526001600160a01b0390921685830190815260243560208201528391839182908490829060400103925af180156102dd576102c5575b50506102966110a0565b9061029f6111fe565b908282106102ab578480f35b516379f1a2a760e01b815292830152602482015260449150fd5b6102ce90611015565b6102d957823861028c565b8280fd5b83513d84823e3d90fd5b825163e2517d3f60e01b815233818601526024810191909152604490fd5b5050346101ac57816003193601126101ac57602090517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b9050346102d957826003193601126102d95754905160089190911c6001600160a01b0316815260209150f35b9050346102d957826003193601126102d9577f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258916020916103ab611297565b6103b3611371565b805460ff1916600117905551338152a180f35b919050346102d957806003193601126102d95761040191356103fc60016103eb610fe9565b93838752866020528620015461134b565b611541565b5080f35b5050346101ac57816003193601126101ac576020906104226111fe565b9051908152f35b9050346102d957826003193601126102d957610443611297565b805460ff8116156104815760ff19169055513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa90602090a180f35b509051638dfc202b60e01b8152fd5b5050346101ac57816003193601126101ac5751908152602090f35b5050346101ac5760203660031901126101ac576104c6610fff565b6104ce611311565b6006549160018060a01b039081841691831680946bffffffffffffffffffffffff60a01b16176006556105256000805160206116c683398151915293848752866020526105206001848920015461134b565b61138f565b5081151580610585575b61055e575b5050507fc1ed2a2e76938d5535836b1406331936bd2440c622b1314c8459f98a5065577b8280a280f35b60016105779161057c948752866020528620015461134b565b6114a8565b50388080610534565b508382141561052f565b5050346101ac5760203660031901126101ac576020906105b56105b0610fff565b61117d565b90519015158152f35b5050346101ac57816003193601126101ac57602090517f5e1c59076ed40a2f2d7578e32a78795e357500600174cc5627a8953d155117228152f35b9050346102d957816003193601126102d95781602093610617610fe9565b92358152808552209060018060a01b0316600052825260ff81600020541690519015158152f35b5050346101ac57816003193601126101ac5760015490516001600160a01b039091168152602090f35b9050346102d95760203660031901126102d95780356001600160a01b038116929083900361075957610697611311565b8051638c89b64f60e01b8152906020828481875afa91821561074f578592610716575b50600254809203610701575050600180546001600160a01b03191683179055507f3f32684a32a11dabdbb8c0177de80aa3ae36a004d75210335b49e544e48cd0aa8280a280f35b51639b6812b960e01b81529182015260249150fd5b9091506020813d602011610747575b816107326020938361103f565b81010312610742575190386106ba565b600080fd5b3d9150610725565b81513d87823e3d90fd5b8380fd5b83346107c55760203660031901126107c557610777610fff565b61077f611311565b600580546001600160a01b0319166001600160a01b039290921691821790557fc92ec24b34ad9d3aa14cd5be87b888d7790d40903da5f44c6367b2fb6cdb20838280a280f35b80fd5b8284346107c557806003193601126107c5575060ff602092541690519015158152f35b5050346101ac57816003193601126101ac57602090516000805160206116c68339815191528152f35b5050346101ac57816003193601126101ac576020905160ff7f0000000000000000000000000000000000000000000000000000000000000006168152f35b8284346107c55760203660031901126107c557506104226020926108957f0000000000000000000000000000000000000000000000000000000000000006611079565b60025491356115b6565b8390346101ac5760203660031901126101ac576108ba611311565b3560025580f35b5050346101ac5760203660031901126101ac5760209160ff9082906001600160a01b036108ec610fff565b1681526007855220541690519015158152f35b5050346101ac57816003193601126101ac576020906104226110a0565b8383346101ac5760603660031901126101ac5782359061093a610fe9565b9360443592600260035414610cb5576002600355610956611371565b6005548351637d35e97760e11b81526001600160a01b039788168482018190526024989390926020929085169083818c81855afa908115610c53578a91610c88575b5015610c72578389526007835260ff878a205416610c5d57865163313ce56760e01b815283818881885afa908115610c53578a91610c18575b508a8487600154168a519283809263b3596f0760e01b82528a8d8301525afa908115610c0e5784908c92610bdc575b5090610a1891610a12610a4a94611079565b916115b6565b610a417f0000000000000000000000000000000000000000000000000000000000000006611079565b600254916115b6565b97808910610bc15750865191838301916323b872dd60e01b8352338c850152604484015260648301526064825260a082019167ffffffffffffffff9281811084821117610bad57885251899182919082875af13d15610ba0573d918211610b8e5790610ad591875191610ac685601f19601f840116018461103f565b82523d8a8584013e5b84611662565b8051918215159283610b75575b505050610b605750849550815460081c1692833b15610b5c5782516340c10f1960e01b815233928101928352602083019190915292849184919082908490829060400103925af1908115610b535750610b3f575b50600160035580f35b610b4890611015565b6107c5578082610b36565b513d84823e3d90fd5b8480fd5b905085925191635274afe760e01b8352820152fd5b610b859350820181019101611061565b15888080610ae2565b634e487b7160e01b8952604186528989fd5b610ad59150606090610acf565b8b604189634e487b7160e01b600052526000fd5b86898c6044938b5193633b5d56ed60e11b8552840152820152fd5b809250868092503d8311610c07575b610bf5818361103f565b81010312610742575183610a4a610a00565b503d610beb565b89513d8d823e3d90fd5b90508381813d8311610c4c575b610c2f818361103f565b81010312610c48575160ff81168103610c48578b6109d1565b8980fd5b503d610c25565b88513d8c823e3d90fd5b86516260f4e760e01b81528087018590528a90fd5b8651632762993f60e11b81528087018590528a90fd5b610ca89150843d8611610cae575b610ca0818361103f565b810190611061565b8b610998565b503d610c96565b509051633ee5aeb560e01b8152fd5b9050346102d957816003193601126102d957610cde610fff565b9160243591821515809303610b5c57610cf5611297565b6005548251637d35e97760e11b81526001600160a01b0395861683820181905295909160209183916024918391165afa908115610db2578691610d93575b5015610d7e57507f6a3f78a37af68267ecb1a43ebdb9a1efb82ceb0f25ab9d945d85ce216406d87d916020918486526007835280862060ff1981541660ff841617905551908152a280f35b836024925191632762993f60e11b8352820152fd5b610dac915060203d602011610cae57610ca0818361103f565b38610d33565b83513d88823e3d90fd5b8383346101ac57806003193601126101ac57610dd6610fe9565b90336001600160a01b03831603610df35750610401919235611541565b5163334bd91960e11b81528390fd5b919050346102d957806003193601126102d9576104019135610e2760016103eb610fe9565b61142f565b919050346102d95760203660031901126102d9576000805160206116c68339815191528084528360205281842033855260205260ff828520541615610f275750610e74611371565b610e7c6110a0565b8254600654859160081c6001600160a01b039081169116813b156102d95784516340c10f1960e01b81526001600160a01b0390911686820190815286356020820152909291839182908490829060400103925af18015610f1d57610f0a575b50610ee46110a0565b91818303610ef0578480f35b5163353d06cf60e21b815292830152602482015260449150fd5b610f1690949194611015565b9238610edb565b83513d87823e3d90fd5b60449291519163e2517d3f60e01b835233908301526024820152fd5b9050346102d95760203660031901126102d957816020936001923581528085522001549051908152f35b5050346101ac57816003193601126101ac5760055490516001600160a01b039091168152602090f35b8491346102d95760203660031901126102d9573563ffffffff60e01b81168091036102d95760209250637965db0b60e01b8114908115610fd8575b5015158152f35b6301ffc9a760e01b14905083610fd1565b602435906001600160a01b038216820361074257565b600435906001600160a01b038216820361074257565b67ffffffffffffffff811161102957604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff82111761102957604052565b90816020910312610742575180151581036107425790565b60ff16604d811161108a57600a0a90565b634e487b7160e01b600052601160045260246000fd5b600480546040516318160ddd60e01b8152916020916001600160a01b03918391859190829060081c85165afa9283156111425760009361114e575b5081600491600654166040519283809263773df15160e01b82525afa91821561114257600092611114575b5050810390811161108a5790565b90809250813d831161113b575b61112b818361103f565b8101031261074257513880611106565b503d611121565b6040513d6000823e3d90fd5b9092508181813d8311611176575b611166818361103f565b81010312610742575191816110db565b503d61115c565b600554604051637d35e97760e11b81526001600160a01b039283166004820181905292909160209183916024918391165afa908115611142576000916111df575b50156111d957600052600760205260ff604060002054161590565b50600090565b6111f8915060203d602011610cae57610ca0818361103f565b386111be565b6005546040516306a61f7560e51b815290602090829060049082906001600160a01b03165afa801561114257600090611263575b6112609150610a417f0000000000000000000000000000000000000000000000000000000000000006611079565b90565b506020813d60201161128f575b8161127d6020938361103f565b81010312610742576112609051611232565b3d9150611270565b3360009081527ff7c9542c591017a21c74b6f3fab6263c7952fc0aaf9db4c22a2a04ddc7f8674f60205260409020547f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a9060ff16156112f35750565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b3360009081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604081205460ff16156112f35750565b80600052600060205260406000203360005260205260ff60406000205416156112f35750565b60ff6004541661137d57565b60405163d93c066560e01b8152600490fd5b6001600160a01b031660008181527fb4e978eb1f17c73554a1f8756605f820ff335cf1d951e33a14aae148be785b3560205260408120549091906000805160206116c68339815191529060ff1661142a57808352826020526040832082845260205260408320600160ff198254161790557f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d339380a4600190565b505090565b9060009180835282602052604083209160018060a01b03169182845260205260ff6040842054161560001461142a57808352826020526040832082845260205260408320600160ff198254161790557f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d339380a4600190565b6001600160a01b031660008181527fb4e978eb1f17c73554a1f8756605f820ff335cf1d951e33a14aae148be785b3560205260408120549091906000805160206116c68339815191529060ff161561142a5780835282602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4600190565b9060009180835282602052604083209160018060a01b03169182845260205260ff60408420541660001461142a5780835282602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4600190565b9091828202916000198482099383808610950394808603951461163e578483111561162c5782910981600003821680920460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029360018380600003040190848311900302920304170290565b60405163227bc15360e01b8152600490fd5b50508092501561164c570490565b634e487b7160e01b600052601260045260246000fd5b90611689575080511561167757805190602001fd5b604051630a12f52160e11b8152600490fd5b815115806116bc575b61169a575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b1561169256febc4aa291e35c786eceb374be398429586e466107044903c4dff7a8687a530070a264697066735822122082301cf235dac7325e1d9210797e5e9fe0e47d36969d6878a922c84cb61ac98064736f6c63430008180033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000624e12de7a97b8cfc1ad1f050a1c9263b1f4febc000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a000000000000000000000000fa7560956807d95dcef22990ddd92e38dbaf5cdd00000000000000000000000049a0c8030ca199f6f246517ae689e3cc0775271a
-----Decoded View---------------
Arg [0] : _collateralVault (address): 0x624E12dE7a97B8cFc1AD1F050a1c9263b1f4FeBC
Arg [1] : _dusd (address): 0x788D96f655735f52c676A133f4dFC53cEC614d4A
Arg [2] : oracle (address): 0xFA7560956807d95DCeF22990DdD92e38DbAf5cDd
Arg [3] : _amoManager (address): 0x49a0c8030Ca199f6F246517aE689E3cC0775271a
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000624e12de7a97b8cfc1ad1f050a1c9263b1f4febc
Arg [1] : 000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a
Arg [2] : 000000000000000000000000fa7560956807d95dcef22990ddd92e38dbaf5cdd
Arg [3] : 00000000000000000000000049a0c8030ca199f6f246517ae689e3cc0775271a
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in FRAX
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.