Overview
S Balance
0 S
S Value
-More Info
Private Name Tags
ContractCreator
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
598517 | 2 days ago | Contract Creation | 0 S |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
BoringSolver
Compiler Version
v0.8.21+commit.d9974bed
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {BoringOnChainQueue, ERC20, SafeTransferLib} from "src/base/Roles/BoringQueue/BoringOnChainQueue.sol"; import {IBoringSolver} from "src/base/Roles/BoringQueue/IBoringSolver.sol"; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {TellerWithMultiAssetSupport} from "src/base/Roles/TellerWithMultiAssetSupport.sol"; import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol"; contract BoringSolver is IBoringSolver, Auth, Multicall { using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; // ========================================= ENUMS ========================================= enum SolveType { BORING_REDEEM, // Fill multiple user requests with a single transaction. BORING_REDEEM_MINT // Fill multiple user requests to redeem shares and mint new shares. } //============================== ERRORS =============================== error BoringSolver___WrongInitiator(); error BoringSolver___BoringVaultTellerMismatch(address boringVault, address teller); error BoringSolver___OnlySelf(); error BoringSolver___FailedToSolve(); error BoringSolver___OnlyQueue(); //============================== IMMUTABLES =============================== BoringOnChainQueue internal immutable queue; constructor(address _owner, address _auth, address _queue) Auth(_owner, Authority(_auth)) { queue = BoringOnChainQueue(_queue); } //============================== ADMIN FUNCTIONS =============================== /** * @notice Allows the owner to rescue tokens from the contract. * @dev This should not normally be used, but it is possible that when performing a MIGRATION_REDEEM, * the redemption of Cellar shares will return assets other than BoringVault shares. * If the amount of assets is significant, it is very likely the solve will revert, but it is * not guaranteed to revert, hence this function. */ function rescueTokens(ERC20 token, uint256 amount) external requiresAuth { if (amount == type(uint256).max) amount = token.balanceOf(address(this)); token.safeTransfer(msg.sender, amount); } //============================== ADMIN SOLVE FUNCTIONS =============================== /** * @notice Solve multiple user requests to redeem Boring Vault shares. */ function boringRedeemSolve(BoringOnChainQueue.OnChainWithdraw[] calldata requests, address teller) external requiresAuth { bytes memory solveData = abi.encode(SolveType.BORING_REDEEM, msg.sender, teller, true); queue.solveOnChainWithdraws(requests, solveData, address(this)); } /** * @notice Solve multiple user requests to redeem Boring Vault shares and mint new Boring Vault shares. * @dev In order for this to work, the fromAccountant must have the toBoringVaults rate provider setup. */ function boringRedeemMintSolve( BoringOnChainQueue.OnChainWithdraw[] calldata requests, address fromTeller, address toTeller, address intermediateAsset ) external requiresAuth { bytes memory solveData = abi.encode(SolveType.BORING_REDEEM_MINT, msg.sender, fromTeller, toTeller, intermediateAsset, true); queue.solveOnChainWithdraws(requests, solveData, address(this)); } //============================== USER SOLVE FUNCTIONS =============================== /** * @notice Allows a user to solve their own request to redeem Boring Vault shares. */ function boringRedeemSelfSolve(BoringOnChainQueue.OnChainWithdraw calldata request, address teller) external requiresAuth { if (request.user != msg.sender) revert BoringSolver___OnlySelf(); BoringOnChainQueue.OnChainWithdraw[] memory requests = new BoringOnChainQueue.OnChainWithdraw[](1); requests[0] = request; bytes memory solveData = abi.encode(SolveType.BORING_REDEEM, msg.sender, teller, false); queue.solveOnChainWithdraws(requests, solveData, address(this)); } /** * @notice Allows a user to solve their own request to redeem Boring Vault shares and mint new Boring Vault shares. * @dev In order for this to work, the fromAccountant must have the toBoringVaults rate provider setup. */ function boringRedeemMintSelfSolve( BoringOnChainQueue.OnChainWithdraw calldata request, address fromTeller, address toTeller, address intermediateAsset ) external requiresAuth { if (request.user != msg.sender) revert BoringSolver___OnlySelf(); BoringOnChainQueue.OnChainWithdraw[] memory requests = new BoringOnChainQueue.OnChainWithdraw[](1); requests[0] = request; bytes memory solveData = abi.encode(SolveType.BORING_REDEEM_MINT, msg.sender, fromTeller, toTeller, intermediateAsset, false); queue.solveOnChainWithdraws(requests, solveData, address(this)); } //============================== IBORINGSOLVER FUNCTIONS =============================== /** * @notice Implementation of the IBoringSolver interface. */ function boringSolve( address initiator, address boringVault, address solveAsset, uint256 totalShares, uint256 requiredAssets, bytes calldata solveData ) external requiresAuth { if (msg.sender != address(queue)) revert BoringSolver___OnlyQueue(); if (initiator != address(this)) revert BoringSolver___WrongInitiator(); SolveType solveType = abi.decode(solveData, (SolveType)); if (solveType == SolveType.BORING_REDEEM) { _boringRedeemSolve(solveData, boringVault, solveAsset, totalShares, requiredAssets); } else if (solveType == SolveType.BORING_REDEEM_MINT) { _boringRedeemMintSolve(solveData, boringVault, solveAsset, totalShares, requiredAssets); } else { // Added for future protection, if another enum is added, txs with that enum will revert, // if no changes are made here. revert BoringSolver___FailedToSolve(); } } //============================== INTERNAL SOLVE FUNCTIONS =============================== /** * @notice Internal helper function to solve multiple user requests to redeem Boring Vault shares. */ function _boringRedeemSolve( bytes calldata solveData, address boringVault, address solveAsset, uint256 totalShares, uint256 requiredAssets ) internal { (, address solverOrigin, TellerWithMultiAssetSupport teller, bool excessToSolver) = abi.decode(solveData, (SolveType, address, TellerWithMultiAssetSupport, bool)); if (boringVault != address(teller.vault())) { revert BoringSolver___BoringVaultTellerMismatch(boringVault, address(teller)); } ERC20 asset = ERC20(solveAsset); // Redeem the Boring Vault shares for Solve Asset. uint256 assetsOut = teller.bulkWithdraw(asset, totalShares, requiredAssets, address(this)); // Transfer excess assets to solver origin or Boring Vault. // Assets are sent to solver to cover gas fees. // But if users are self solving, then the excess assets go to the Boring Vault. if (excessToSolver) { asset.safeTransfer(solverOrigin, assetsOut - requiredAssets); } else { asset.safeTransfer(boringVault, assetsOut - requiredAssets); } // Approve Boring Queue to spend the required assets. asset.approve(address(queue), requiredAssets); } /** * @notice Internal helper function to solve multiple user requests to redeem Boring Vault shares and mint new Boring Vault shares. */ function _boringRedeemMintSolve( bytes calldata solveData, address fromBoringVault, address toBoringVault, uint256 totalShares, uint256 requiredShares ) internal { ( , address solverOrigin, TellerWithMultiAssetSupport fromTeller, TellerWithMultiAssetSupport toTeller, ERC20 intermediateAsset, bool excessToSolver ) = abi.decode( solveData, (SolveType, address, TellerWithMultiAssetSupport, TellerWithMultiAssetSupport, ERC20, bool) ); if (fromBoringVault != address(fromTeller.vault())) { revert BoringSolver___BoringVaultTellerMismatch(fromBoringVault, address(fromTeller)); } if (toBoringVault != address(toTeller.vault())) { revert BoringSolver___BoringVaultTellerMismatch(toBoringVault, address(toTeller)); } // Redeem the fromBoringVault shares for Intermediate Asset. uint256 excessAssets = fromTeller.bulkWithdraw(intermediateAsset, totalShares, 0, address(this)); { // Determine how many assets are needed to mint requiredAssets worth of toBoringVault shares. // Note mulDivUp is used to ensure we always mint enough assets to cover the requiredShares. uint256 assetsToMintRequiredShares = requiredShares.mulDivUp( toTeller.accountant().getRateInQuoteSafe(intermediateAsset), BoringOnChainQueue(queue).ONE_SHARE() ); // Remove assetsToMintRequiredShares from excessAssets. excessAssets = excessAssets - assetsToMintRequiredShares; // Approve toBoringVault to spend the Intermediate Asset. intermediateAsset.safeApprove(toBoringVault, assetsToMintRequiredShares); // Mint to BoringVault shares using Intermediate Asset. toTeller.bulkDeposit(intermediateAsset, assetsToMintRequiredShares, requiredShares, address(this)); } // Transfer excess assets to solver origin or Boring Vault. // Assets are sent to solver to cover gas fees. // But if users are self solving, then the excess assets go to the from Boring Vault. if (excessToSolver) { intermediateAsset.safeTransfer(solverOrigin, excessAssets); } else { intermediateAsset.safeTransfer(fromBoringVault, excessAssets); } // Approve Boring Queue to spend the required assets. ERC20(toBoringVault).approve(address(queue), requiredShares); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) abstract contract Auth { event OwnershipTransferred(address indexed user, address indexed newOwner); event AuthorityUpdated(address indexed user, Authority indexed newAuthority); address public owner; Authority public authority; constructor(address _owner, Authority _authority) { owner = _owner; authority = _authority; emit OwnershipTransferred(msg.sender, _owner); emit AuthorityUpdated(msg.sender, _authority); } modifier requiresAuth() virtual { require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED"); _; } function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas. // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be // aware that this makes protected functions uncallable even to the owner if the authority is out of order. return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; } function setAuthority(Authority newAuthority) public virtual { // We check if the caller is the owner first because we want to ensure they can // always swap out the authority even if it's reverting or using up a lot of gas. require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); authority = newAuthority; emit AuthorityUpdated(msg.sender, newAuthority); } function transferOwnership(address newOwner) public virtual requiresAuth { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); } } /// @notice A generic interface for a contract which provides authorization data to an Auth instance. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) interface Authority { function canCall( address user, address target, bytes4 functionSig ) external view returns (bool); }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {WETH} from "@solmate/tokens/WETH.sol"; import {BoringVault} from "src/base/BoringVault.sol"; import {AccountantWithRateProviders} from "src/base/Roles/AccountantWithRateProviders.sol"; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol"; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {ReentrancyGuard} from "@solmate/utils/ReentrancyGuard.sol"; import {IPausable} from "src/interfaces/IPausable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IBoringSolver} from "src/base/Roles/BoringQueue/IBoringSolver.sol"; contract BoringOnChainQueue is Auth, ReentrancyGuard, IPausable { using EnumerableSet for EnumerableSet.Bytes32Set; using SafeTransferLib for BoringVault; using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; // ========================================= STRUCTS ========================================= /** * @param allowWithdraws Whether or not withdraws are allowed for this asset. * @param secondsToMaturity The time in seconds it takes for the asset to mature. * @param minimumSecondsToDeadline The minimum time in seconds a withdraw request must be valid for before it is expired * @param minDiscount The minimum discount allowed for a withdraw request. * @param maxDiscount The maximum discount allowed for a withdraw request. * @param minimumShares The minimum amount of shares that can be withdrawn. */ struct WithdrawAsset { bool allowWithdraws; uint24 secondsToMaturity; uint24 minimumSecondsToDeadline; uint16 minDiscount; uint16 maxDiscount; uint96 minimumShares; } /** * @param nonce The nonce of the request, used to make it impossible for request Ids to be repeated. * @param user The user that made the request. * @param assetOut The asset that the user wants to withdraw. * @param amountOfShares The amount of shares the user wants to withdraw. * @param amountOfAssets The amount of assets the user will receive. * @param creationTime The time the request was made. * @param secondsToMaturity The time in seconds it takes for the asset to mature. * @param secondsToDeadline The time in seconds the request is valid for. */ struct OnChainWithdraw { uint96 nonce; // read from state, used to make it impossible for request Ids to be repeated. address user; // msg.sender address assetOut; // input sanitized uint128 amountOfShares; // input transfered in uint128 amountOfAssets; // derived from amountOfShares and price uint40 creationTime; // time withdraw was made uint24 secondsToMaturity; // in contract, from withdrawAsset? uint24 secondsToDeadline; // in contract, from withdrawAsset? To get the deadline you take the creationTime add seconds to maturity, add the secondsToDeadline } // ========================================= CONSTANTS ========================================= /** * @notice The maximum discount allowed for a withdraw asset. */ uint16 internal constant MAX_DISCOUNT = 0.3e4; /** * @notice The maximum time in seconds a withdraw asset can take to mature. */ uint24 internal constant MAXIMUM_SECONDS_TO_MATURITY = 30 days; /** * @notice Caps the minimum time in seconds a withdraw request must be valid for before it is expired. */ uint24 internal constant MAXIMUM_MINIMUM_SECONDS_TO_DEADLINE = 30 days; // ========================================= MODIFIERS ========================================= /** * @notice Ensure that the request user is the same as the message sender. */ modifier onlyRequestUser(address requestUser, address msgSender) { if (requestUser != msgSender) revert BoringOnChainQueue__BadUser(); _; } // ========================================= GLOBAL STATE ========================================= /** * @notice Open Zeppelin EnumerableSet to store all withdraw requests, by there request Id. */ EnumerableSet.Bytes32Set private _withdrawRequests; /** * @notice Mapping of asset addresses to WithdrawAssets. */ mapping(address => WithdrawAsset) public withdrawAssets; /** * @notice The nonce of the next request. * @dev The purpose of this nonce is to prevent request Ids from being repeated. * @dev Start at 1, since 0 is considered invalid. * @dev When incrementing the nonce, an unchecked block is used to save gas. * This is safe because you can not feasibly make a request, and then cause an overflow * in the same block such that you can make 2 requests with the same request Id. * And even if you did, the tx would revert with a keccak256 collision error. */ uint96 public nonce = 1; /** * @notice Whether or not the contract is paused. */ bool public isPaused; //============================== ERRORS =============================== error BoringOnChainQueue__Paused(); error BoringOnChainQueue__WithdrawsNotAllowedForAsset(); error BoringOnChainQueue__BadDiscount(); error BoringOnChainQueue__BadShareAmount(); error BoringOnChainQueue__BadDeadline(); error BoringOnChainQueue__BadUser(); error BoringOnChainQueue__DeadlinePassed(); error BoringOnChainQueue__NotMatured(); error BoringOnChainQueue__Keccak256Collision(); error BoringOnChainQueue__RequestNotFound(); error BoringOnChainQueue__PermitFailedAndAllowanceTooLow(); error BoringOnChainQueue__MAX_DISCOUNT(); error BoringOnChainQueue__MAXIMUM_MINIMUM_SECONDS_TO_DEADLINE(); error BoringOnChainQueue__SolveAssetMismatch(); error BoringOnChainQueue__Overflow(); error BoringOnChainQueue__MAXIMUM_SECONDS_TO_MATURITY(); error BoringOnChainQueue__BadInput(); error BoringOnChainQueue__RescueCannotTakeSharesFromActiveRequests(); //============================== EVENTS =============================== event OnChainWithdrawRequested( bytes32 indexed requestId, address indexed user, address indexed assetOut, uint96 nonce, uint128 amountOfShares, uint128 amountOfAssets, uint40 creationTime, uint24 secondsToMaturity, uint24 secondsToDeadline ); event OnChainWithdrawCancelled(bytes32 indexed requestId, address indexed user, uint256 timestamp); event OnChainWithdrawSolved(bytes32 indexed requestId, address indexed user, uint256 timestamp); event WithdrawAssetSetup( address indexed assetOut, uint24 secondsToMaturity, uint24 minimumSecondsToDeadline, uint16 minDiscount, uint16 maxDiscount, uint96 minimumShares ); event WithdrawAssetStopped(address indexed assetOut); event WithdrawAssetUpdated( address indexed assetOut, uint24 minimumSecondsToDeadline, uint24 secondsToMaturity, uint16 minDiscount, uint16 maxDiscount, uint96 minimumShares ); event Paused(); event Unpaused(); //============================== IMMUTABLES =============================== /** * @notice The BoringVault contract to withdraw from. */ BoringVault public immutable boringVault; /** * @notice The AccountantWithRateProviders contract to get rates from. */ AccountantWithRateProviders public immutable accountant; /** * @notice One BoringVault share. */ uint256 public immutable ONE_SHARE; constructor(address _owner, address _auth, address payable _boringVault, address _accountant) Auth(_owner, Authority(_auth)) { boringVault = BoringVault(_boringVault); ONE_SHARE = 10 ** boringVault.decimals(); accountant = AccountantWithRateProviders(_accountant); } //=============================== ADMIN FUNCTIONS ================================ /** * @notice Allows the owner to rescue tokens from the contract. * @dev The owner can only withdraw BoringVault shares if they are accidentally sent to this contract. * Shares from active withdraw requests are not withdrawable. * @param token The token to rescue. * @param amount The amount to rescue. * @param to The address to send the rescued tokens to. * @param activeRequests The active withdraw requests, query `getWithdrawRequests`, or read events to get them. * @dev Provided activeRequests must match the order of active requests in the queue. */ function rescueTokens(ERC20 token, uint256 amount, address to, OnChainWithdraw[] calldata activeRequests) external requiresAuth { if (address(token) == address(boringVault)) { bytes32[] memory requestIds = _withdrawRequests.values(); uint256 requestIdsLength = requestIds.length; if (activeRequests.length != requestIdsLength) revert BoringOnChainQueue__BadInput(); // Iterate through provided activeRequests, and hash each one to compare to the requestIds. // Also track the sum of shares to make sure it is less than or equal to the amount. uint256 activeRequestShareSum; for (uint256 i = 0; i < requestIdsLength; ++i) { if (keccak256(abi.encode(activeRequests[i])) != requestIds[i]) revert BoringOnChainQueue__BadInput(); activeRequestShareSum += activeRequests[i].amountOfShares; } uint256 freeShares = boringVault.balanceOf(address(this)) - activeRequestShareSum; if (amount == type(uint256).max) amount = freeShares; else if (amount > freeShares) revert BoringOnChainQueue__RescueCannotTakeSharesFromActiveRequests(); } else { if (amount == type(uint256).max) amount = token.balanceOf(address(this)); } token.safeTransfer(to, amount); } /** * @notice Pause this contract, which prevents future calls to any functions that * create new requests, or solve active requests. * @dev Callable by MULTISIG_ROLE. */ function pause() external requiresAuth { isPaused = true; emit Paused(); } /** * @notice Unpause this contract, which allows future calls to any functions that * create new requests, or solve active requests. * @dev Callable by MULTISIG_ROLE. */ function unpause() external requiresAuth { isPaused = false; emit Unpaused(); } /** * @notice Update a new withdraw asset or existing. * @dev Callable by MULTISIG_ROLE. * @param assetOut The asset to withdraw. * @param secondsToMaturity The time in seconds it takes for the withdraw to mature. * @param minimumSecondsToDeadline The minimum time in seconds a withdraw request must be valid for before it is expired. * @param minDiscount The minimum discount allowed for a withdraw request. * @param maxDiscount The maximum discount allowed for a withdraw request. * @param minimumShares The minimum amount of shares that can be withdrawn. */ function updateWithdrawAsset( address assetOut, uint24 secondsToMaturity, uint24 minimumSecondsToDeadline, uint16 minDiscount, uint16 maxDiscount, uint96 minimumShares ) external requiresAuth { // Validate input. if (maxDiscount > MAX_DISCOUNT) revert BoringOnChainQueue__MAX_DISCOUNT(); if (secondsToMaturity > MAXIMUM_SECONDS_TO_MATURITY) { revert BoringOnChainQueue__MAXIMUM_SECONDS_TO_MATURITY(); } if (minimumSecondsToDeadline > MAXIMUM_MINIMUM_SECONDS_TO_DEADLINE) { revert BoringOnChainQueue__MAXIMUM_MINIMUM_SECONDS_TO_DEADLINE(); } if (minDiscount > maxDiscount) revert BoringOnChainQueue__BadDiscount(); // Make sure accountant can price it. accountant.getRateInQuoteSafe(ERC20(assetOut)); withdrawAssets[assetOut] = WithdrawAsset({ allowWithdraws: true, secondsToMaturity: secondsToMaturity, minimumSecondsToDeadline: minimumSecondsToDeadline, minDiscount: minDiscount, maxDiscount: maxDiscount, minimumShares: minimumShares }); emit WithdrawAssetUpdated( assetOut, secondsToMaturity, minimumSecondsToDeadline, minDiscount, maxDiscount, minimumShares ); } /** * @notice Stop withdraws in an asset. * @dev Callable by MULTISIG_ROLE. * @param assetOut The asset to stop withdraws in. */ function stopWithdrawsInAsset(address assetOut) external requiresAuth { withdrawAssets[assetOut].allowWithdraws = false; emit WithdrawAssetStopped(assetOut); } /** * @notice Cancel multiple user withdraws. * @dev Callable by STRATEGIST_MULTISIG_ROLE. */ function cancelUserWithdraws(OnChainWithdraw[] calldata requests) external requiresAuth returns (bytes32[] memory canceledRequestIds) { uint256 requestsLength = requests.length; canceledRequestIds = new bytes32[](requestsLength); for (uint256 i = 0; i < requestsLength; ++i) { canceledRequestIds[i] = _cancelOnChainWithdraw(requests[i]); } } //=============================== USER FUNCTIONS ================================ /** * @notice Request an on-chain withdraw. * @param assetOut The asset to withdraw. * @param amountOfShares The amount of shares to withdraw. * @param discount The discount to apply to the withdraw in bps. * @param secondsToDeadline The time in seconds the request is valid for. * @return requestId The request Id. */ function requestOnChainWithdraw(address assetOut, uint128 amountOfShares, uint16 discount, uint24 secondsToDeadline) external virtual requiresAuth returns (bytes32 requestId) { WithdrawAsset memory withdrawAsset = withdrawAssets[assetOut]; _beforeNewRequest(withdrawAsset, amountOfShares, discount, secondsToDeadline); boringVault.safeTransferFrom(msg.sender, address(this), amountOfShares); (requestId,) = _queueOnChainWithdraw( msg.sender, assetOut, amountOfShares, discount, withdrawAsset.secondsToMaturity, secondsToDeadline ); } /** * @notice Request an on-chain withdraw with permit. * @param assetOut The asset to withdraw. * @param amountOfShares The amount of shares to withdraw. * @param discount The discount to apply to the withdraw in bps. * @param secondsToDeadline The time in seconds the request is valid for. * @param permitDeadline The deadline for the permit. * @param v The v value of the permit signature. * @param r The r value of the permit signature. * @param s The s value of the permit signature. * @return requestId The request Id. */ function requestOnChainWithdrawWithPermit( address assetOut, uint128 amountOfShares, uint16 discount, uint24 secondsToDeadline, uint256 permitDeadline, uint8 v, bytes32 r, bytes32 s ) external virtual requiresAuth returns (bytes32 requestId) { WithdrawAsset memory withdrawAsset = withdrawAssets[assetOut]; _beforeNewRequest(withdrawAsset, amountOfShares, discount, secondsToDeadline); try boringVault.permit(msg.sender, address(this), amountOfShares, permitDeadline, v, r, s) {} catch { if (boringVault.allowance(msg.sender, address(this)) < amountOfShares) { revert BoringOnChainQueue__PermitFailedAndAllowanceTooLow(); } } boringVault.safeTransferFrom(msg.sender, address(this), amountOfShares); (requestId,) = _queueOnChainWithdraw( msg.sender, assetOut, amountOfShares, discount, withdrawAsset.secondsToMaturity, secondsToDeadline ); } /** * @notice Cancel an on-chain withdraw. * @param request The request to cancel. * @return requestId The request Id. */ function cancelOnChainWithdraw(OnChainWithdraw memory request) external virtual requiresAuth returns (bytes32 requestId) { requestId = _cancelOnChainWithdrawWithUserCheck(request); } /** * @notice Replace an on-chain withdraw. * @param oldRequest The request to replace. * @param discount The discount to apply to the new withdraw request in bps. * @param secondsToDeadline The time in seconds the new withdraw request is valid for. * @return oldRequestId The request Id of the old withdraw request. * @return newRequestId The request Id of the new withdraw request. */ function replaceOnChainWithdraw(OnChainWithdraw memory oldRequest, uint16 discount, uint24 secondsToDeadline) external virtual requiresAuth returns (bytes32 oldRequestId, bytes32 newRequestId) { (oldRequestId, newRequestId) = _replaceOnChainWithdrawWithUserCheck(oldRequest, discount, secondsToDeadline); } //============================== SOLVER FUNCTIONS =============================== /** * @notice Solve multiple on-chain withdraws. * @dev If `solveData` is empty, this contract will skip the callback function. * @param requests The requests to solve. * @param solveData The data to use to solve the requests. * @param solver The address of the solver. */ function solveOnChainWithdraws(OnChainWithdraw[] calldata requests, bytes calldata solveData, address solver) external requiresAuth { if (isPaused) revert BoringOnChainQueue__Paused(); ERC20 solveAsset = ERC20(requests[0].assetOut); uint256 requiredAssets; uint256 totalShares; uint256 requestsLength = requests.length; for (uint256 i = 0; i < requestsLength; ++i) { if (address(solveAsset) != requests[i].assetOut) revert BoringOnChainQueue__SolveAssetMismatch(); uint256 maturity = requests[i].creationTime + requests[i].secondsToMaturity; if (block.timestamp < maturity) revert BoringOnChainQueue__NotMatured(); uint256 deadline = maturity + requests[i].secondsToDeadline; if (block.timestamp > deadline) revert BoringOnChainQueue__DeadlinePassed(); requiredAssets += requests[i].amountOfAssets; totalShares += requests[i].amountOfShares; bytes32 requestId = _dequeueOnChainWithdraw(requests[i]); emit OnChainWithdrawSolved(requestId, requests[i].user, block.timestamp); } // Transfer shares to solver. boringVault.safeTransfer(solver, totalShares); // Run callback function if data is provided. if (solveData.length > 0) { IBoringSolver(solver).boringSolve( msg.sender, address(boringVault), address(solveAsset), totalShares, requiredAssets, solveData ); } for (uint256 i = 0; i < requestsLength; ++i) { solveAsset.safeTransferFrom(solver, requests[i].user, requests[i].amountOfAssets); } } //============================== VIEW FUNCTIONS =============================== /** * @notice Get all request Ids currently in the queue. * @dev Includes requests that are not mature, matured, and expired. But does not include requests that have been solved. * @return requestIds The request Ids. */ function getRequestIds() public view returns (bytes32[] memory) { return _withdrawRequests.values(); } /** * @notice Get the request Id for a request. * @param request The request. * @return requestId The request Id. */ function getRequestId(OnChainWithdraw calldata request) external pure returns (bytes32 requestId) { return keccak256(abi.encode(request)); } /** * @notice Preview assets out from a withdraw request. */ function previewAssetsOut(address assetOut, uint128 amountOfShares, uint16 discount) public view returns (uint128 amountOfAssets128) { uint256 price = accountant.getRateInQuoteSafe(ERC20(assetOut)); price = price.mulDivDown(1e4 - discount, 1e4); uint256 amountOfAssets = uint256(amountOfShares).mulDivDown(price, ONE_SHARE); if (amountOfAssets > type(uint128).max) revert BoringOnChainQueue__Overflow(); amountOfAssets128 = uint128(amountOfAssets); } //============================= INTERNAL FUNCTIONS ============================== /** * @notice Before a new request is made, validate the input. * @param withdrawAsset The withdraw asset. * @param amountOfShares The amount of shares to withdraw. * @param discount The discount to apply to the withdraw in bps. * @param secondsToDeadline The time in seconds the request is valid for. */ function _beforeNewRequest( WithdrawAsset memory withdrawAsset, uint128 amountOfShares, uint16 discount, uint24 secondsToDeadline ) internal view virtual { if (isPaused) revert BoringOnChainQueue__Paused(); if (!withdrawAsset.allowWithdraws) revert BoringOnChainQueue__WithdrawsNotAllowedForAsset(); if (discount < withdrawAsset.minDiscount || discount > withdrawAsset.maxDiscount) { revert BoringOnChainQueue__BadDiscount(); } if (amountOfShares < withdrawAsset.minimumShares) revert BoringOnChainQueue__BadShareAmount(); if (secondsToDeadline < withdrawAsset.minimumSecondsToDeadline) revert BoringOnChainQueue__BadDeadline(); } /** * @notice Cancel an on-chain withdraw. * @dev Verifies that the request user is the same as the msg.sender. * @param request The request to cancel. * @return requestId The request Id. */ function _cancelOnChainWithdrawWithUserCheck(OnChainWithdraw memory request) internal virtual onlyRequestUser(request.user, msg.sender) returns (bytes32 requestId) { requestId = _cancelOnChainWithdraw(request); } /** * @notice Cancel an on-chain withdraw. * @param request The request to cancel. * @return requestId The request Id. */ function _cancelOnChainWithdraw(OnChainWithdraw memory request) internal virtual returns (bytes32 requestId) { requestId = _dequeueOnChainWithdraw(request); boringVault.safeTransfer(request.user, request.amountOfShares); emit OnChainWithdrawCancelled(requestId, request.user, block.timestamp); } /** * @notice Replace an on-chain withdraw. * @dev Verifies that the request user is the same as the msg.sender. * @param oldRequest The request to replace. * @param discount The discount to apply to the new withdraw request in bps. * @param secondsToDeadline The time in seconds the new withdraw request is valid for. * @return oldRequestId The request Id of the old withdraw request. * @return newRequestId The request Id of the new withdraw request. */ function _replaceOnChainWithdrawWithUserCheck( OnChainWithdraw memory oldRequest, uint16 discount, uint24 secondsToDeadline ) internal virtual onlyRequestUser(oldRequest.user, msg.sender) returns (bytes32 oldRequestId, bytes32 newRequestId) { (oldRequestId, newRequestId) = _replaceOnChainWithdraw(oldRequest, discount, secondsToDeadline); } /** * @notice Replace an on-chain withdraw. * @param oldRequest The request to replace. * @param discount The discount to apply to the new withdraw request in bps. * @param secondsToDeadline The time in seconds the new withdraw request is valid for. * @return oldRequestId The request Id of the old withdraw request. * @return newRequestId The request Id of the new withdraw request. */ function _replaceOnChainWithdraw(OnChainWithdraw memory oldRequest, uint16 discount, uint24 secondsToDeadline) internal virtual onlyRequestUser(oldRequest.user, msg.sender) returns (bytes32 oldRequestId, bytes32 newRequestId) { WithdrawAsset memory withdrawAsset = withdrawAssets[oldRequest.assetOut]; _beforeNewRequest(withdrawAsset, oldRequest.amountOfShares, discount, secondsToDeadline); oldRequestId = _dequeueOnChainWithdraw(oldRequest); emit OnChainWithdrawCancelled(oldRequestId, oldRequest.user, block.timestamp); // Create new request. (newRequestId,) = _queueOnChainWithdraw( oldRequest.user, oldRequest.assetOut, oldRequest.amountOfShares, discount, withdrawAsset.secondsToMaturity, secondsToDeadline ); } /** * @notice Queue an on-chain withdraw. * @dev Reverts if the request is already in the queue. Though this should be impossible. * @param user The user that made the request. * @param assetOut The asset to withdraw. * @param amountOfShares The amount of shares to withdraw. * @param discount The discount to apply to the withdraw in bps. * @param secondsToMaturity The time in seconds it takes for the asset to mature. * @param secondsToDeadline The time in seconds the request is valid for. * @return requestId The request Id. */ function _queueOnChainWithdraw( address user, address assetOut, uint128 amountOfShares, uint16 discount, uint24 secondsToMaturity, uint24 secondsToDeadline ) internal virtual returns (bytes32 requestId, OnChainWithdraw memory req) { // Create new request. uint96 requestNonce; // See nonce definition for unchecked safety. unchecked { // Set request nonce as current nonce, then increment nonce. requestNonce = nonce++; } uint128 amountOfAssets128 = previewAssetsOut(assetOut, amountOfShares, discount); uint40 timeNow = uint40(block.timestamp); // Safe to cast to uint40 as it won't overflow for 10s of thousands of years req = OnChainWithdraw({ nonce: requestNonce, user: user, assetOut: assetOut, amountOfShares: amountOfShares, amountOfAssets: amountOfAssets128, creationTime: timeNow, secondsToMaturity: secondsToMaturity, secondsToDeadline: secondsToDeadline }); requestId = keccak256(abi.encode(req)); bool addedToSet = _withdrawRequests.add(requestId); if (!addedToSet) revert BoringOnChainQueue__Keccak256Collision(); emit OnChainWithdrawRequested( requestId, user, assetOut, requestNonce, amountOfShares, amountOfAssets128, timeNow, secondsToMaturity, secondsToDeadline ); } /** * @notice Dequeue an on-chain withdraw. * @dev Reverts if the request is not in the queue. * @dev Does not remove the request from the onChainWithdraws mapping, so that * it can be referenced later by off-chain systems if needed. * @param request The request to dequeue. * @return requestId The request Id. */ function _dequeueOnChainWithdraw(OnChainWithdraw memory request) internal virtual returns (bytes32 requestId) { // Remove request from queue. requestId = keccak256(abi.encode(request)); bool removedFromSet = _withdrawRequests.remove(requestId); if (!removedFromSet) revert BoringOnChainQueue__RequestNotFound(); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; interface IBoringSolver { function boringSolve( address initiator, address boringVault, address solveAsset, uint256 totalShares, uint256 requiredAssets, bytes calldata solveData ) external; }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Arithmetic library with operations for fixed-point numbers. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) library FixedPointMathLib { /*////////////////////////////////////////////////////////////// SIMPLIFIED FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ uint256 internal constant MAX_UINT256 = 2**256 - 1; uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. } function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. } function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. } function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. } /*////////////////////////////////////////////////////////////// LOW LEVEL FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ function mulDivDown( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // Divide x * y by the denominator. z := div(mul(x, y), denominator) } } function mulDivUp( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // If x * y modulo the denominator is strictly greater than 0, // 1 is added to round up the division of x * y by the denominator. z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) } } function rpow( uint256 x, uint256 n, uint256 scalar ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { switch x case 0 { switch n case 0 { // 0 ** 0 = 1 z := scalar } default { // 0 ** n = 0 z := 0 } } default { switch mod(n, 2) case 0 { // If n is even, store scalar in z for now. z := scalar } default { // If n is odd, store x in z for now. z := x } // Shifting right by 1 is like dividing by 2. let half := shr(1, scalar) for { // Shift n right by 1 before looping to halve it. n := shr(1, n) } n { // Shift n right by 1 each iteration to halve it. n := shr(1, n) } { // Revert immediately if x ** 2 would overflow. // Equivalent to iszero(eq(div(xx, x), x)) here. if shr(128, x) { revert(0, 0) } // Store x squared. let xx := mul(x, x) // Round to the nearest number. let xxRound := add(xx, half) // Revert if xx + half overflowed. if lt(xxRound, xx) { revert(0, 0) } // Set x to scaled xxRound. x := div(xxRound, scalar) // If n is even: if mod(n, 2) { // Compute z * x. let zx := mul(z, x) // If z * x overflowed: if iszero(eq(div(zx, x), z)) { // Revert if x is non-zero. if iszero(iszero(x)) { revert(0, 0) } } // Round to the nearest number. let zxRound := add(zx, half) // Revert if zx + half overflowed. if lt(zxRound, zx) { revert(0, 0) } // Return properly scaled zxRound. z := div(zxRound, scalar) } } } } } /*////////////////////////////////////////////////////////////// GENERAL NUMBER UTILITIES //////////////////////////////////////////////////////////////*/ function sqrt(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { let y := x // We start y at x, which will help us make our initial estimate. z := 181 // The "correct" value is 1, but this saves a multiplication later. // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. // We check y >= 2^(k + 8) but shift right by k bits // each branch to ensure that if x >= 256, then y >= 256. if iszero(lt(y, 0x10000000000000000000000000000000000)) { y := shr(128, y) z := shl(64, z) } if iszero(lt(y, 0x1000000000000000000)) { y := shr(64, y) z := shl(32, z) } if iszero(lt(y, 0x10000000000)) { y := shr(32, y) z := shl(16, z) } if iszero(lt(y, 0x1000000)) { y := shr(16, y) z := shl(8, z) } // Goal was to get z*z*y within a small factor of x. More iterations could // get y in a tighter range. Currently, we will have y in [256, 256*2^16). // We ensured y >= 256 so that the relative difference between y and y+1 is small. // That's not possible if x < 256 but we can just verify those cases exhaustively. // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. // There is no overflow risk here since y < 2^136 after the first branch above. z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) // If x+1 is a perfect square, the Babylonian method cycles between // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. // If you don't care whether the floor or ceil square root is returned, you can remove this statement. z := sub(z, lt(div(x, z), z)) } } function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Mod x by y. Note this will return // 0 instead of reverting if y is zero. z := mod(x, y) } } function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { // Divide x by y. Note this will return // 0 instead of reverting if y is zero. r := div(x, y) } } function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Add 1 to x * y if x % y > 0. Note this will // return 0 instead of reverting if y is zero. z := add(gt(mod(x, y), 0), div(x, y)) } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {WETH} from "@solmate/tokens/WETH.sol"; import {BoringVault} from "src/base/BoringVault.sol"; import {AccountantWithRateProviders} from "src/base/Roles/AccountantWithRateProviders.sol"; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol"; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {ReentrancyGuard} from "@solmate/utils/ReentrancyGuard.sol"; import {IPausable} from "src/interfaces/IPausable.sol"; contract TellerWithMultiAssetSupport is Auth, BeforeTransferHook, ReentrancyGuard, IPausable { using FixedPointMathLib for uint256; using SafeTransferLib for ERC20; using SafeTransferLib for WETH; // ========================================= STRUCTS ========================================= /** * @param allowDeposits bool indicating whether or not deposits are allowed for this asset. * @param allowWithdraws bool indicating whether or not withdraws are allowed for this asset. * @param sharePremium uint16 indicating the premium to apply to the shares minted. * where 40 represents a 40bps reduction in shares minted using this asset. */ struct Asset { bool allowDeposits; bool allowWithdraws; uint16 sharePremium; } // ========================================= CONSTANTS ========================================= /** * @notice Native address used to tell the contract to handle native asset deposits. */ address internal constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /** * @notice The maximum possible share lock period. */ uint256 internal constant MAX_SHARE_LOCK_PERIOD = 3 days; /** * @notice The maximum possible share premium that can be set using `updateAssetData`. * @dev 1,000 or 10% */ uint16 internal constant MAX_SHARE_PREMIUM = 1_000; // ========================================= STATE ========================================= /** * @notice Mapping ERC20s to their assetData. */ mapping(ERC20 => Asset) public assetData; /** * @notice The deposit nonce used to map to a deposit hash. */ uint96 public depositNonce; /** * @notice After deposits, shares are locked to the msg.sender's address * for `shareLockPeriod`. * @dev During this time all trasnfers from msg.sender will revert, and * deposits are refundable. */ uint64 public shareLockPeriod; /** * @notice Used to pause calls to `deposit` and `depositWithPermit`. */ bool public isPaused; /** * @dev Maps deposit nonce to keccak256(address receiver, address depositAsset, uint256 depositAmount, uint256 shareAmount, uint256 timestamp, uint256 shareLockPeriod). */ mapping(uint256 => bytes32) public publicDepositHistory; /** * @notice Maps user address to the time their shares will be unlocked. */ mapping(address => uint256) public shareUnlockTime; /** * @notice Mapping `from` address to a bool to deny them from transferring shares. */ mapping(address => bool) public fromDenyList; /** * @notice Mapping `to` address to a bool to deny them from receiving shares. */ mapping(address => bool) public toDenyList; /** * @notice Mapping `opeartor` address to a bool to deny them from calling `transfer` or `transferFrom`. */ mapping(address => bool) public operatorDenyList; //============================== ERRORS =============================== error TellerWithMultiAssetSupport__ShareLockPeriodTooLong(); error TellerWithMultiAssetSupport__SharesAreLocked(); error TellerWithMultiAssetSupport__SharesAreUnLocked(); error TellerWithMultiAssetSupport__BadDepositHash(); error TellerWithMultiAssetSupport__AssetNotSupported(); error TellerWithMultiAssetSupport__ZeroAssets(); error TellerWithMultiAssetSupport__MinimumMintNotMet(); error TellerWithMultiAssetSupport__MinimumAssetsNotMet(); error TellerWithMultiAssetSupport__PermitFailedAndAllowanceTooLow(); error TellerWithMultiAssetSupport__ZeroShares(); error TellerWithMultiAssetSupport__DualDeposit(); error TellerWithMultiAssetSupport__Paused(); error TellerWithMultiAssetSupport__TransferDenied(address from, address to, address operator); error TellerWithMultiAssetSupport__SharePremiumTooLarge(); error TellerWithMultiAssetSupport__CannotDepositNative(); //============================== EVENTS =============================== event Paused(); event Unpaused(); event AssetDataUpdated(address indexed asset, bool allowDeposits, bool allowWithdraws, uint16 sharePremium); event Deposit( uint256 indexed nonce, address indexed receiver, address indexed depositAsset, uint256 depositAmount, uint256 shareAmount, uint256 depositTimestamp, uint256 shareLockPeriodAtTimeOfDeposit ); event BulkDeposit(address indexed asset, uint256 depositAmount); event BulkWithdraw(address indexed asset, uint256 shareAmount); event DepositRefunded(uint256 indexed nonce, bytes32 depositHash, address indexed user); event DenyFrom(address indexed user); event DenyTo(address indexed user); event DenyOperator(address indexed user); event AllowFrom(address indexed user); event AllowTo(address indexed user); event AllowOperator(address indexed user); // =============================== MODIFIERS =============================== /** * @notice Reverts if the deposit asset is the native asset. */ modifier revertOnNativeDeposit(address depositAsset) { if (depositAsset == NATIVE) revert TellerWithMultiAssetSupport__CannotDepositNative(); _; } //============================== IMMUTABLES =============================== /** * @notice The BoringVault this contract is working with. */ BoringVault public immutable vault; /** * @notice The AccountantWithRateProviders this contract is working with. */ AccountantWithRateProviders public immutable accountant; /** * @notice One share of the BoringVault. */ uint256 internal immutable ONE_SHARE; /** * @notice The native wrapper contract. */ WETH public immutable nativeWrapper; constructor(address _owner, address _vault, address _accountant, address _weth) Auth(_owner, Authority(address(0))) { vault = BoringVault(payable(_vault)); ONE_SHARE = 10 ** vault.decimals(); accountant = AccountantWithRateProviders(_accountant); nativeWrapper = WETH(payable(_weth)); } // ========================================= ADMIN FUNCTIONS ========================================= /** * @notice Pause this contract, which prevents future calls to `deposit` and `depositWithPermit`. * @dev Callable by MULTISIG_ROLE. */ function pause() external requiresAuth { isPaused = true; emit Paused(); } /** * @notice Unpause this contract, which allows future calls to `deposit` and `depositWithPermit`. * @dev Callable by MULTISIG_ROLE. */ function unpause() external requiresAuth { isPaused = false; emit Unpaused(); } /** * @notice Updates the asset data for a given asset. * @dev The accountant must also support pricing this asset, else the `deposit` call will revert. * @dev Callable by OWNER_ROLE. */ function updateAssetData(ERC20 asset, bool allowDeposits, bool allowWithdraws, uint16 sharePremium) external requiresAuth { if (sharePremium > MAX_SHARE_PREMIUM) revert TellerWithMultiAssetSupport__SharePremiumTooLarge(); assetData[asset] = Asset(allowDeposits, allowWithdraws, sharePremium); emit AssetDataUpdated(address(asset), allowDeposits, allowWithdraws, sharePremium); } /** * @notice Sets the share lock period. * @dev This not only locks shares to the user address, but also serves as the pending deposit period, where deposits can be reverted. * @dev If a new shorter share lock period is set, users with pending share locks could make a new deposit to receive 1 wei shares, * and have their shares unlock sooner than their original deposit allows. This state would allow for the user deposit to be refunded, * but only if they have not transferred their shares out of there wallet. This is an accepted limitation, and should be known when decreasing * the share lock period. * @dev Callable by OWNER_ROLE. */ function setShareLockPeriod(uint64 _shareLockPeriod) external requiresAuth { if (_shareLockPeriod > MAX_SHARE_LOCK_PERIOD) revert TellerWithMultiAssetSupport__ShareLockPeriodTooLong(); shareLockPeriod = _shareLockPeriod; } /** * @notice Deny a user from transferring or receiving shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function denyAll(address user) external requiresAuth { fromDenyList[user] = true; toDenyList[user] = true; operatorDenyList[user] = true; emit DenyFrom(user); emit DenyTo(user); emit DenyOperator(user); } /** * @notice Allow a user to transfer or receive shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function allowAll(address user) external requiresAuth { fromDenyList[user] = false; toDenyList[user] = false; operatorDenyList[user] = false; emit AllowFrom(user); emit AllowTo(user); emit AllowOperator(user); } /** * @notice Deny a user from transferring shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function denyFrom(address user) external requiresAuth { fromDenyList[user] = true; emit DenyFrom(user); } /** * @notice Allow a user to transfer shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function allowFrom(address user) external requiresAuth { fromDenyList[user] = false; emit AllowFrom(user); } /** * @notice Deny a user from receiving shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function denyTo(address user) external requiresAuth { toDenyList[user] = true; emit DenyTo(user); } /** * @notice Allow a user to receive shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function allowTo(address user) external requiresAuth { toDenyList[user] = false; emit AllowTo(user); } /** * @notice Deny an operator from transferring shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function denyOperator(address user) external requiresAuth { operatorDenyList[user] = true; emit DenyOperator(user); } /** * @notice Allow an operator to transfer shares. * @dev Callable by OWNER_ROLE, and DENIER_ROLE. */ function allowOperator(address user) external requiresAuth { operatorDenyList[user] = false; emit AllowOperator(user); } // ========================================= BeforeTransferHook FUNCTIONS ========================================= /** * @notice Implement beforeTransfer hook to check if shares are locked, or if `from`, `to`, or `operator` are on the deny list. * @notice If share lock period is set to zero, then users will be able to mint and transfer in the same tx. * if this behavior is not desired then a share lock period of >=1 should be used. */ function beforeTransfer(address from, address to, address operator) public view virtual { if (fromDenyList[from] || toDenyList[to] || operatorDenyList[operator]) { revert TellerWithMultiAssetSupport__TransferDenied(from, to, operator); } if (shareUnlockTime[from] > block.timestamp) revert TellerWithMultiAssetSupport__SharesAreLocked(); } // ========================================= REVERT DEPOSIT FUNCTIONS ========================================= /** * @notice Allows DEPOSIT_REFUNDER_ROLE to revert a pending deposit. * @dev Once a deposit share lock period has passed, it can no longer be reverted. * @dev It is possible the admin does not setup the BoringVault to call the transfer hook, * but this contract can still be saving share lock state. In the event this happens * deposits are still refundable if the user has not transferred their shares. * But there is no guarantee that the user has not transferred their shares. * @dev Callable by STRATEGIST_MULTISIG_ROLE. */ function refundDeposit( uint256 nonce, address receiver, address depositAsset, uint256 depositAmount, uint256 shareAmount, uint256 depositTimestamp, uint256 shareLockUpPeriodAtTimeOfDeposit ) external requiresAuth { if ((block.timestamp - depositTimestamp) >= shareLockUpPeriodAtTimeOfDeposit) { // Shares are already unlocked, so we can not revert deposit. revert TellerWithMultiAssetSupport__SharesAreUnLocked(); } bytes32 depositHash = keccak256( abi.encode( receiver, depositAsset, depositAmount, shareAmount, depositTimestamp, shareLockUpPeriodAtTimeOfDeposit ) ); if (publicDepositHistory[nonce] != depositHash) revert TellerWithMultiAssetSupport__BadDepositHash(); // Delete hash to prevent refund gas. delete publicDepositHistory[nonce]; // If deposit used native asset, send user back wrapped native asset. depositAsset = depositAsset == NATIVE ? address(nativeWrapper) : depositAsset; // Burn shares and refund assets to receiver. vault.exit(receiver, ERC20(depositAsset), depositAmount, receiver, shareAmount); emit DepositRefunded(nonce, depositHash, receiver); } // ========================================= USER FUNCTIONS ========================================= /** * @notice Allows users to deposit into the BoringVault, if this contract is not paused. * @dev Publicly callable. */ function deposit(ERC20 depositAsset, uint256 depositAmount, uint256 minimumMint) external payable requiresAuth nonReentrant returns (uint256 shares) { Asset memory asset = _beforeDeposit(depositAsset); address from; if (address(depositAsset) == NATIVE) { if (msg.value == 0) revert TellerWithMultiAssetSupport__ZeroAssets(); nativeWrapper.deposit{value: msg.value}(); // Set depositAmount to msg.value. depositAmount = msg.value; nativeWrapper.safeApprove(address(vault), depositAmount); // Update depositAsset to nativeWrapper. depositAsset = nativeWrapper; // Set from to this address since user transferred value. from = address(this); } else { if (msg.value > 0) revert TellerWithMultiAssetSupport__DualDeposit(); from = msg.sender; } shares = _erc20Deposit(depositAsset, depositAmount, minimumMint, from, msg.sender, asset); _afterPublicDeposit(msg.sender, depositAsset, depositAmount, shares, shareLockPeriod); } /** * @notice Allows users to deposit into BoringVault using permit. * @dev Publicly callable. */ function depositWithPermit( ERC20 depositAsset, uint256 depositAmount, uint256 minimumMint, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external requiresAuth nonReentrant revertOnNativeDeposit(address(depositAsset)) returns (uint256 shares) { Asset memory asset = _beforeDeposit(depositAsset); _handlePermit(depositAsset, depositAmount, deadline, v, r, s); shares = _erc20Deposit(depositAsset, depositAmount, minimumMint, msg.sender, msg.sender, asset); _afterPublicDeposit(msg.sender, depositAsset, depositAmount, shares, shareLockPeriod); } /** * @notice Allows on ramp role to deposit into this contract. * @dev Does NOT support native deposits. * @dev Callable by SOLVER_ROLE. */ function bulkDeposit(ERC20 depositAsset, uint256 depositAmount, uint256 minimumMint, address to) external requiresAuth nonReentrant returns (uint256 shares) { Asset memory asset = _beforeDeposit(depositAsset); shares = _erc20Deposit(depositAsset, depositAmount, minimumMint, msg.sender, to, asset); emit BulkDeposit(address(depositAsset), depositAmount); } /** * @notice Allows off ramp role to withdraw from this contract. * @dev Callable by SOLVER_ROLE. */ function bulkWithdraw(ERC20 withdrawAsset, uint256 shareAmount, uint256 minimumAssets, address to) external requiresAuth returns (uint256 assetsOut) { if (isPaused) revert TellerWithMultiAssetSupport__Paused(); Asset memory asset = assetData[withdrawAsset]; if (!asset.allowWithdraws) revert TellerWithMultiAssetSupport__AssetNotSupported(); if (shareAmount == 0) revert TellerWithMultiAssetSupport__ZeroShares(); assetsOut = shareAmount.mulDivDown(accountant.getRateInQuoteSafe(withdrawAsset), ONE_SHARE); if (assetsOut < minimumAssets) revert TellerWithMultiAssetSupport__MinimumAssetsNotMet(); vault.exit(to, withdrawAsset, assetsOut, msg.sender, shareAmount); emit BulkWithdraw(address(withdrawAsset), shareAmount); } // ========================================= INTERNAL HELPER FUNCTIONS ========================================= /** * @notice Implements a common ERC20 deposit into BoringVault. */ function _erc20Deposit( ERC20 depositAsset, uint256 depositAmount, uint256 minimumMint, address from, address to, Asset memory asset ) internal returns (uint256 shares) { if (depositAmount == 0) revert TellerWithMultiAssetSupport__ZeroAssets(); shares = depositAmount.mulDivDown(ONE_SHARE, accountant.getRateInQuoteSafe(depositAsset)); shares = asset.sharePremium > 0 ? shares.mulDivDown(1e4 - asset.sharePremium, 1e4) : shares; if (shares < minimumMint) revert TellerWithMultiAssetSupport__MinimumMintNotMet(); vault.enter(from, depositAsset, depositAmount, to, shares); } /** * @notice Handle pre-deposit checks. */ function _beforeDeposit(ERC20 depositAsset) internal view returns (Asset memory asset) { if (isPaused) revert TellerWithMultiAssetSupport__Paused(); asset = assetData[depositAsset]; if (!asset.allowDeposits) revert TellerWithMultiAssetSupport__AssetNotSupported(); } /** * @notice Handle share lock logic, and event. */ function _afterPublicDeposit( address user, ERC20 depositAsset, uint256 depositAmount, uint256 shares, uint256 currentShareLockPeriod ) internal { // Increment then assign as its slightly more gas efficient. uint256 nonce = ++depositNonce; // Only set share unlock time and history if share lock period is greater than 0. if (currentShareLockPeriod > 0) { shareUnlockTime[user] = block.timestamp + currentShareLockPeriod; publicDepositHistory[nonce] = keccak256( abi.encode(user, depositAsset, depositAmount, shares, block.timestamp, currentShareLockPeriod) ); } emit Deposit(nonce, user, address(depositAsset), depositAmount, shares, block.timestamp, currentShareLockPeriod); } /** * @notice Handle permit logic. */ function _handlePermit(ERC20 depositAsset, uint256 depositAmount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) internal { try depositAsset.permit(msg.sender, address(vault), depositAmount, deadline, v, r, s) {} catch { if (depositAsset.allowance(msg.sender, address(vault)) < depositAmount) { revert TellerWithMultiAssetSupport__PermitFailedAndAllowanceTooLow(); } } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Multicall.sol) pragma solidity ^0.8.20; import {Address} from "./Address.sol"; import {Context} from "./Context.sol"; /** * @dev Provides a function to batch together multiple calls in a single external call. * * Consider any assumption about calldata validation performed by the sender may be violated if it's not especially * careful about sending transactions invoking {multicall}. For example, a relay address that filters function * selectors won't filter calls nested within a {multicall} operation. * * NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {_msgSender}). * If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data` * to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of * {_msgSender} are not propagated to subcalls. */ abstract contract Multicall is Context { /** * @dev Receives and executes a batch of function calls on this contract. * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { bytes memory context = msg.sender == _msgSender() ? new bytes(0) : msg.data[msg.data.length - _contextSuffixLength():]; results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context)); } return results; } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "./ERC20.sol"; import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; /// @notice Minimalist and modern Wrapped Ether implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol) /// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) contract WETH is ERC20("Wrapped Ether", "WETH", 18) { using SafeTransferLib for address; event Deposit(address indexed from, uint256 amount); event Withdrawal(address indexed to, uint256 amount); function deposit() public payable virtual { _mint(msg.sender, msg.value); emit Deposit(msg.sender, msg.value); } function withdraw(uint256 amount) public virtual { _burn(msg.sender, amount); emit Withdrawal(msg.sender, amount); msg.sender.safeTransferETH(amount); } receive() external payable virtual { deposit(); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol"; import {Auth, Authority} from "@solmate/auth/Auth.sol"; contract BoringVault is ERC20, Auth, ERC721Holder, ERC1155Holder { using Address for address; using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; // ========================================= STATE ========================================= /** * @notice Contract responsbile for implementing `beforeTransfer`. */ BeforeTransferHook public hook; //============================== EVENTS =============================== event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares); event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares); //============================== CONSTRUCTOR =============================== constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) Auth(_owner, Authority(address(0))) {} //============================== MANAGE =============================== /** * @notice Allows manager to make an arbitrary function call from this contract. * @dev Callable by MANAGER_ROLE. */ function manage(address target, bytes calldata data, uint256 value) external requiresAuth returns (bytes memory result) { result = target.functionCallWithValue(data, value); } /** * @notice Allows manager to make arbitrary function calls from this contract. * @dev Callable by MANAGER_ROLE. */ function manage(address[] calldata targets, bytes[] calldata data, uint256[] calldata values) external requiresAuth returns (bytes[] memory results) { uint256 targetsLength = targets.length; results = new bytes[](targetsLength); for (uint256 i; i < targetsLength; ++i) { results[i] = targets[i].functionCallWithValue(data[i], values[i]); } } //============================== ENTER =============================== /** * @notice Allows minter to mint shares, in exchange for assets. * @dev If assetAmount is zero, no assets are transferred in. * @dev Callable by MINTER_ROLE. */ function enter(address from, ERC20 asset, uint256 assetAmount, address to, uint256 shareAmount) external requiresAuth { // Transfer assets in if (assetAmount > 0) asset.safeTransferFrom(from, address(this), assetAmount); // Mint shares. _mint(to, shareAmount); emit Enter(from, address(asset), assetAmount, to, shareAmount); } //============================== EXIT =============================== /** * @notice Allows burner to burn shares, in exchange for assets. * @dev If assetAmount is zero, no assets are transferred out. * @dev Callable by BURNER_ROLE. */ function exit(address to, ERC20 asset, uint256 assetAmount, address from, uint256 shareAmount) external requiresAuth { // Burn shares. _burn(from, shareAmount); // Transfer assets out. if (assetAmount > 0) asset.safeTransfer(to, assetAmount); emit Exit(to, address(asset), assetAmount, from, shareAmount); } //============================== BEFORE TRANSFER HOOK =============================== /** * @notice Sets the share locker. * @notice If set to zero address, the share locker logic is disabled. * @dev Callable by OWNER_ROLE. */ function setBeforeTransferHook(address _hook) external requiresAuth { hook = BeforeTransferHook(_hook); } /** * @notice Call `beforeTransferHook` passing in `from` `to`, and `msg.sender`. */ function _callBeforeTransfer(address from, address to) internal view { if (address(hook) != address(0)) hook.beforeTransfer(from, to, msg.sender); } function transfer(address to, uint256 amount) public override returns (bool) { _callBeforeTransfer(msg.sender, to); return super.transfer(to, amount); } function transferFrom(address from, address to, uint256 amount) public override returns (bool) { _callBeforeTransfer(from, to); return super.transferFrom(from, to, amount); } //============================== RECEIVE =============================== receive() external payable {} }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {IRateProvider} from "src/interfaces/IRateProvider.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {BoringVault} from "src/base/BoringVault.sol"; import {Auth, Authority} from "@solmate/auth/Auth.sol"; import {IPausable} from "src/interfaces/IPausable.sol"; contract AccountantWithRateProviders is Auth, IRateProvider, IPausable { using FixedPointMathLib for uint256; using SafeTransferLib for ERC20; // ========================================= STRUCTS ========================================= /** * @param payoutAddress the address `claimFees` sends fees to * @param highwaterMark the highest value of the BoringVault's share price * @param feesOwedInBase total pending fees owed in terms of base * @param totalSharesLastUpdate total amount of shares the last exchange rate update * @param exchangeRate the current exchange rate in terms of base * @param allowedExchangeRateChangeUpper the max allowed change to exchange rate from an update * @param allowedExchangeRateChangeLower the min allowed change to exchange rate from an update * @param lastUpdateTimestamp the block timestamp of the last exchange rate update * @param isPaused whether or not this contract is paused * @param minimumUpdateDelayInSeconds the minimum amount of time that must pass between * exchange rate updates, such that the update won't trigger the contract to be paused * @param platformFee the platform fee * @param performanceFee the performance fee */ struct AccountantState { address payoutAddress; uint96 highwaterMark; uint128 feesOwedInBase; uint128 totalSharesLastUpdate; uint96 exchangeRate; uint16 allowedExchangeRateChangeUpper; uint16 allowedExchangeRateChangeLower; uint64 lastUpdateTimestamp; bool isPaused; uint24 minimumUpdateDelayInSeconds; uint16 platformFee; uint16 performanceFee; } /** * @param isPeggedToBase whether or not the asset is 1:1 with the base asset * @param rateProvider the rate provider for this asset if `isPeggedToBase` is false */ struct RateProviderData { bool isPeggedToBase; IRateProvider rateProvider; } // ========================================= STATE ========================================= /** * @notice Store the accountant state in 3 packed slots. */ AccountantState public accountantState; /** * @notice Maps ERC20s to their RateProviderData. */ mapping(ERC20 => RateProviderData) public rateProviderData; //============================== ERRORS =============================== error AccountantWithRateProviders__UpperBoundTooSmall(); error AccountantWithRateProviders__LowerBoundTooLarge(); error AccountantWithRateProviders__PlatformFeeTooLarge(); error AccountantWithRateProviders__PerformanceFeeTooLarge(); error AccountantWithRateProviders__Paused(); error AccountantWithRateProviders__ZeroFeesOwed(); error AccountantWithRateProviders__OnlyCallableByBoringVault(); error AccountantWithRateProviders__UpdateDelayTooLarge(); error AccountantWithRateProviders__ExchangeRateAboveHighwaterMark(); //============================== EVENTS =============================== event Paused(); event Unpaused(); event DelayInSecondsUpdated(uint24 oldDelay, uint24 newDelay); event UpperBoundUpdated(uint16 oldBound, uint16 newBound); event LowerBoundUpdated(uint16 oldBound, uint16 newBound); event PlatformFeeUpdated(uint16 oldFee, uint16 newFee); event PerformanceFeeUpdated(uint16 oldFee, uint16 newFee); event PayoutAddressUpdated(address oldPayout, address newPayout); event RateProviderUpdated(address asset, bool isPegged, address rateProvider); event ExchangeRateUpdated(uint96 oldRate, uint96 newRate, uint64 currentTime); event FeesClaimed(address indexed feeAsset, uint256 amount); event HighwaterMarkReset(); //============================== IMMUTABLES =============================== /** * @notice The base asset rates are provided in. */ ERC20 public immutable base; /** * @notice The decimals rates are provided in. */ uint8 public immutable decimals; /** * @notice The BoringVault this accountant is working with. * Used to determine share supply for fee calculation. */ BoringVault public immutable vault; /** * @notice One share of the BoringVault. */ uint256 internal immutable ONE_SHARE; constructor( address _owner, address _vault, address payoutAddress, uint96 startingExchangeRate, address _base, uint16 allowedExchangeRateChangeUpper, uint16 allowedExchangeRateChangeLower, uint24 minimumUpdateDelayInSeconds, uint16 platformFee, uint16 performanceFee ) Auth(_owner, Authority(address(0))) { base = ERC20(_base); decimals = ERC20(_base).decimals(); vault = BoringVault(payable(_vault)); ONE_SHARE = 10 ** vault.decimals(); accountantState = AccountantState({ payoutAddress: payoutAddress, highwaterMark: startingExchangeRate, feesOwedInBase: 0, totalSharesLastUpdate: uint128(vault.totalSupply()), exchangeRate: startingExchangeRate, allowedExchangeRateChangeUpper: allowedExchangeRateChangeUpper, allowedExchangeRateChangeLower: allowedExchangeRateChangeLower, lastUpdateTimestamp: uint64(block.timestamp), isPaused: false, minimumUpdateDelayInSeconds: minimumUpdateDelayInSeconds, platformFee: platformFee, performanceFee: performanceFee }); } // ========================================= ADMIN FUNCTIONS ========================================= /** * @notice Pause this contract, which prevents future calls to `updateExchangeRate`, and any safe rate * calls will revert. * @dev Callable by MULTISIG_ROLE. */ function pause() external requiresAuth { accountantState.isPaused = true; emit Paused(); } /** * @notice Unpause this contract, which allows future calls to `updateExchangeRate`, and any safe rate * calls will stop reverting. * @dev Callable by MULTISIG_ROLE. */ function unpause() external requiresAuth { accountantState.isPaused = false; emit Unpaused(); } /** * @notice Update the minimum time delay between `updateExchangeRate` calls. * @dev There are no input requirements, as it is possible the admin would want * the exchange rate updated as frequently as needed. * @dev Callable by OWNER_ROLE. */ function updateDelay(uint24 minimumUpdateDelayInSeconds) external requiresAuth { if (minimumUpdateDelayInSeconds > 14 days) revert AccountantWithRateProviders__UpdateDelayTooLarge(); uint24 oldDelay = accountantState.minimumUpdateDelayInSeconds; accountantState.minimumUpdateDelayInSeconds = minimumUpdateDelayInSeconds; emit DelayInSecondsUpdated(oldDelay, minimumUpdateDelayInSeconds); } /** * @notice Update the allowed upper bound change of exchange rate between `updateExchangeRateCalls`. * @dev Callable by OWNER_ROLE. */ function updateUpper(uint16 allowedExchangeRateChangeUpper) external requiresAuth { if (allowedExchangeRateChangeUpper < 1e4) revert AccountantWithRateProviders__UpperBoundTooSmall(); uint16 oldBound = accountantState.allowedExchangeRateChangeUpper; accountantState.allowedExchangeRateChangeUpper = allowedExchangeRateChangeUpper; emit UpperBoundUpdated(oldBound, allowedExchangeRateChangeUpper); } /** * @notice Update the allowed lower bound change of exchange rate between `updateExchangeRateCalls`. * @dev Callable by OWNER_ROLE. */ function updateLower(uint16 allowedExchangeRateChangeLower) external requiresAuth { if (allowedExchangeRateChangeLower > 1e4) revert AccountantWithRateProviders__LowerBoundTooLarge(); uint16 oldBound = accountantState.allowedExchangeRateChangeLower; accountantState.allowedExchangeRateChangeLower = allowedExchangeRateChangeLower; emit LowerBoundUpdated(oldBound, allowedExchangeRateChangeLower); } /** * @notice Update the platform fee to a new value. * @dev Callable by OWNER_ROLE. */ function updatePlatformFee(uint16 platformFee) external requiresAuth { if (platformFee > 0.2e4) revert AccountantWithRateProviders__PlatformFeeTooLarge(); uint16 oldFee = accountantState.platformFee; accountantState.platformFee = platformFee; emit PlatformFeeUpdated(oldFee, platformFee); } /** * @notice Update the performance fee to a new value. * @dev Callable by OWNER_ROLE. */ function updatePerformanceFee(uint16 performanceFee) external requiresAuth { if (performanceFee > 0.5e4) revert AccountantWithRateProviders__PerformanceFeeTooLarge(); uint16 oldFee = accountantState.performanceFee; accountantState.performanceFee = performanceFee; emit PerformanceFeeUpdated(oldFee, performanceFee); } /** * @notice Update the payout address fees are sent to. * @dev Callable by OWNER_ROLE. */ function updatePayoutAddress(address payoutAddress) external requiresAuth { address oldPayout = accountantState.payoutAddress; accountantState.payoutAddress = payoutAddress; emit PayoutAddressUpdated(oldPayout, payoutAddress); } /** * @notice Update the rate provider data for a specific `asset`. * @dev Rate providers must return rates in terms of `base` or * an asset pegged to base and they must use the same decimals * as `asset`. * @dev Callable by OWNER_ROLE. */ function setRateProviderData(ERC20 asset, bool isPeggedToBase, address rateProvider) external requiresAuth { rateProviderData[asset] = RateProviderData({isPeggedToBase: isPeggedToBase, rateProvider: IRateProvider(rateProvider)}); emit RateProviderUpdated(address(asset), isPeggedToBase, rateProvider); } /** * @notice Reset the highwater mark to the current exchange rate. * @dev Callable by OWNER_ROLE. */ function resetHighwaterMark() external virtual requiresAuth { AccountantState storage state = accountantState; if (state.exchangeRate > state.highwaterMark) { revert AccountantWithRateProviders__ExchangeRateAboveHighwaterMark(); } uint64 currentTime = uint64(block.timestamp); uint256 currentTotalShares = vault.totalSupply(); _calculateFeesOwed(state, state.exchangeRate, state.exchangeRate, currentTotalShares, currentTime); state.totalSharesLastUpdate = uint128(currentTotalShares); state.highwaterMark = accountantState.exchangeRate; state.lastUpdateTimestamp = currentTime; emit HighwaterMarkReset(); } // ========================================= UPDATE EXCHANGE RATE/FEES FUNCTIONS ========================================= /** * @notice Updates this contract exchangeRate. * @dev If new exchange rate is outside of accepted bounds, or if not enough time has passed, this * will pause the contract, and this function will NOT calculate fees owed. * @dev Callable by UPDATE_EXCHANGE_RATE_ROLE. */ function updateExchangeRate(uint96 newExchangeRate) external virtual requiresAuth { ( bool shouldPause, AccountantState storage state, uint64 currentTime, uint256 currentExchangeRate, uint256 currentTotalShares ) = _beforeUpdateExchangeRate(newExchangeRate); if (shouldPause) { // Instead of reverting, pause the contract. This way the exchange rate updater is able to update the exchange rate // to a better value, and pause it. state.isPaused = true; } else { _calculateFeesOwed(state, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime); } newExchangeRate = _setExchangeRate(newExchangeRate, state); state.totalSharesLastUpdate = uint128(currentTotalShares); state.lastUpdateTimestamp = currentTime; emit ExchangeRateUpdated(uint96(currentExchangeRate), newExchangeRate, currentTime); } /** * @notice Claim pending fees. * @dev This function must be called by the BoringVault. * @dev This function will lose precision if the exchange rate * decimals is greater than the feeAsset's decimals. */ function claimFees(ERC20 feeAsset) external { if (msg.sender != address(vault)) revert AccountantWithRateProviders__OnlyCallableByBoringVault(); AccountantState storage state = accountantState; if (state.isPaused) revert AccountantWithRateProviders__Paused(); if (state.feesOwedInBase == 0) revert AccountantWithRateProviders__ZeroFeesOwed(); // Determine amount of fees owed in feeAsset. uint256 feesOwedInFeeAsset; RateProviderData memory data = rateProviderData[feeAsset]; if (address(feeAsset) == address(base)) { feesOwedInFeeAsset = state.feesOwedInBase; } else { uint8 feeAssetDecimals = ERC20(feeAsset).decimals(); uint256 feesOwedInBaseUsingFeeAssetDecimals = _changeDecimals(state.feesOwedInBase, decimals, feeAssetDecimals); if (data.isPeggedToBase) { feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals; } else { uint256 rate = data.rateProvider.getRate(); feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals.mulDivDown(10 ** feeAssetDecimals, rate); } } // Zero out fees owed. state.feesOwedInBase = 0; // Transfer fee asset to payout address. feeAsset.safeTransferFrom(msg.sender, state.payoutAddress, feesOwedInFeeAsset); emit FeesClaimed(address(feeAsset), feesOwedInFeeAsset); } // ========================================= VIEW FUNCTIONS ========================================= /** * @notice Get this BoringVault's current rate in the base. */ function getRate() public view returns (uint256 rate) { rate = accountantState.exchangeRate; } /** * @notice Get this BoringVault's current rate in the base. * @dev Revert if paused. */ function getRateSafe() external view returns (uint256 rate) { if (accountantState.isPaused) revert AccountantWithRateProviders__Paused(); rate = getRate(); } /** * @notice Get this BoringVault's current rate in the provided quote. * @dev `quote` must have its RateProviderData set, else this will revert. * @dev This function will lose precision if the exchange rate * decimals is greater than the quote's decimals. */ function getRateInQuote(ERC20 quote) public view returns (uint256 rateInQuote) { if (address(quote) == address(base)) { rateInQuote = accountantState.exchangeRate; } else { RateProviderData memory data = rateProviderData[quote]; uint8 quoteDecimals = ERC20(quote).decimals(); uint256 exchangeRateInQuoteDecimals = _changeDecimals(accountantState.exchangeRate, decimals, quoteDecimals); if (data.isPeggedToBase) { rateInQuote = exchangeRateInQuoteDecimals; } else { uint256 quoteRate = data.rateProvider.getRate(); uint256 oneQuote = 10 ** quoteDecimals; rateInQuote = oneQuote.mulDivDown(exchangeRateInQuoteDecimals, quoteRate); } } } /** * @notice Get this BoringVault's current rate in the provided quote. * @dev `quote` must have its RateProviderData set, else this will revert. * @dev Revert if paused. */ function getRateInQuoteSafe(ERC20 quote) external view returns (uint256 rateInQuote) { if (accountantState.isPaused) revert AccountantWithRateProviders__Paused(); rateInQuote = getRateInQuote(quote); } /** * @notice Preview the result of an update to the exchange rate. * @return updateWillPause Whether the update will pause the contract. * @return newFeesOwedInBase The new fees owed in base. * @return totalFeesOwedInBase The total fees owed in base. */ function previewUpdateExchangeRate(uint96 newExchangeRate) external view virtual returns (bool updateWillPause, uint256 newFeesOwedInBase, uint256 totalFeesOwedInBase) { ( bool shouldPause, AccountantState storage state, uint64 currentTime, uint256 currentExchangeRate, uint256 currentTotalShares ) = _beforeUpdateExchangeRate(newExchangeRate); updateWillPause = shouldPause; totalFeesOwedInBase = state.feesOwedInBase; if (!shouldPause) { (uint256 platformFeesOwedInBase, uint256 shareSupplyToUse) = _calculatePlatformFee( state.totalSharesLastUpdate, state.lastUpdateTimestamp, state.platformFee, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime ); uint256 performanceFeesOwedInBase; if (newExchangeRate > state.highwaterMark) { (performanceFeesOwedInBase,) = _calculatePerformanceFee( newExchangeRate, shareSupplyToUse, state.highwaterMark, state.performanceFee ); } newFeesOwedInBase = platformFeesOwedInBase + performanceFeesOwedInBase; totalFeesOwedInBase += newFeesOwedInBase; } } // ========================================= INTERNAL HELPER FUNCTIONS ========================================= /** * @notice Used to change the decimals of precision used for an amount. */ function _changeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) { if (fromDecimals == toDecimals) { return amount; } else if (fromDecimals < toDecimals) { return amount * 10 ** (toDecimals - fromDecimals); } else { return amount / 10 ** (fromDecimals - toDecimals); } } /** * @notice Check if the new exchange rate is outside of the allowed bounds or if not enough time has passed. */ function _beforeUpdateExchangeRate(uint96 newExchangeRate) internal view returns ( bool shouldPause, AccountantState storage state, uint64 currentTime, uint256 currentExchangeRate, uint256 currentTotalShares ) { state = accountantState; if (state.isPaused) revert AccountantWithRateProviders__Paused(); currentTime = uint64(block.timestamp); currentExchangeRate = state.exchangeRate; currentTotalShares = vault.totalSupply(); shouldPause = currentTime < state.lastUpdateTimestamp + state.minimumUpdateDelayInSeconds || newExchangeRate > currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeUpper, 1e4) || newExchangeRate < currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeLower, 1e4); } /** * @notice Set the exchange rate. */ function _setExchangeRate(uint96 newExchangeRate, AccountantState storage state) internal virtual returns (uint96) { state.exchangeRate = newExchangeRate; return newExchangeRate; } /** * @notice Calculate platform fees. */ function _calculatePlatformFee( uint128 totalSharesLastUpdate, uint64 lastUpdateTimestamp, uint16 platformFee, uint96 newExchangeRate, uint256 currentExchangeRate, uint256 currentTotalShares, uint64 currentTime ) internal view returns (uint256 platformFeesOwedInBase, uint256 shareSupplyToUse) { shareSupplyToUse = currentTotalShares; // Use the minimum between current total supply and total supply for last update. if (totalSharesLastUpdate < shareSupplyToUse) { shareSupplyToUse = totalSharesLastUpdate; } // Determine platform fees owned. if (platformFee > 0) { uint256 timeDelta = currentTime - lastUpdateTimestamp; uint256 minimumAssets = newExchangeRate > currentExchangeRate ? shareSupplyToUse.mulDivDown(currentExchangeRate, ONE_SHARE) : shareSupplyToUse.mulDivDown(newExchangeRate, ONE_SHARE); uint256 platformFeesAnnual = minimumAssets.mulDivDown(platformFee, 1e4); platformFeesOwedInBase = platformFeesAnnual.mulDivDown(timeDelta, 365 days); } } /** * @notice Calculate performance fees. */ function _calculatePerformanceFee( uint96 newExchangeRate, uint256 shareSupplyToUse, uint96 datum, uint16 performanceFee ) internal view returns (uint256 performanceFeesOwedInBase, uint256 yieldEarned) { uint256 changeInExchangeRate = newExchangeRate - datum; yieldEarned = changeInExchangeRate.mulDivDown(shareSupplyToUse, ONE_SHARE); if (performanceFee > 0) { performanceFeesOwedInBase = yieldEarned.mulDivDown(performanceFee, 1e4); } } /** * @notice Calculate fees owed in base. * @dev This function will update the highwater mark if the new exchange rate is higher. */ function _calculateFeesOwed( AccountantState storage state, uint96 newExchangeRate, uint256 currentExchangeRate, uint256 currentTotalShares, uint64 currentTime ) internal virtual { // Only update fees if we are not paused. // Update fee accounting. (uint256 newFeesOwedInBase, uint256 shareSupplyToUse) = _calculatePlatformFee( state.totalSharesLastUpdate, state.lastUpdateTimestamp, state.platformFee, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime ); // Account for performance fees. if (newExchangeRate > state.highwaterMark) { (uint256 performanceFeesOwedInBase,) = _calculatePerformanceFee(newExchangeRate, shareSupplyToUse, state.highwaterMark, state.performanceFee); // Add performance fees to fees owed. newFeesOwedInBase += performanceFeesOwedInBase; // Always update the highwater mark if the new exchange rate is higher. // This way if we are not iniitiall taking performance fees, we can start taking them // without back charging them on past performance. state.highwaterMark = newExchangeRate; } state.feesOwedInBase += uint128(newFeesOwedInBase); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; interface BeforeTransferHook { function beforeTransfer(address from, address to, address operator) external view; }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Gas optimized reentrancy protection for smart contracts. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) abstract contract ReentrancyGuard { uint256 private locked = 1; modifier nonReentrant() virtual { require(locked == 1, "REENTRANCY"); locked = 2; _; locked = 1; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; interface IPausable { function pause() external; function unpause() external; }
// 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 // 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) (token/ERC721/utils/ERC721Holder.sol) pragma solidity ^0.8.20; import {IERC721Receiver} from "../IERC721Receiver.sol"; /** * @dev Implementation of the {IERC721Receiver} interface. * * Accepts all token transfers. * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or * {IERC721-setApprovalForAll}. */ abstract contract ERC721Holder is IERC721Receiver { /** * @dev See {IERC721Receiver-onERC721Received}. * * Always returns `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC721Received.selector; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol) pragma solidity ^0.8.20; import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; import {IERC1155Receiver} from "../IERC1155Receiver.sol"; /** * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC1155 tokens. * * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be * stuck. */ abstract contract ERC1155Holder is ERC165, IERC1155Receiver { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); } function onERC1155Received( address, address, uint256, uint256, bytes memory ) public virtual override returns (bytes4) { return this.onERC1155Received.selector; } function onERC1155BatchReceived( address, address, uint256[] memory, uint256[] memory, bytes memory ) public virtual override returns (bytes4) { return this.onERC1155BatchReceived.selector; } }
// SPDX-License-Identifier: UNLICENSED // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.8.0; interface IRateProvider { function getRate() external view returns (uint256); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.20; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be * reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// 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) (token/ERC1155/IERC1155Receiver.sol) pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Interface that must be implemented by smart contracts in order to receive * ERC-1155 token transfers. */ interface IERC1155Receiver is IERC165 { /** * @dev Handles the receipt of a single ERC1155 token type. This function is * called at the end of a `safeTransferFrom` after the balance has been updated. * * NOTE: To accept the transfer, this must return * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` * (i.e. 0xf23a6e61, or its own function selector). * * @param operator The address which initiated the transfer (i.e. msg.sender) * @param from The address which previously owned the token * @param id The ID of the token being transferred * @param value The amount of tokens being transferred * @param data Additional data with no specified format * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed */ function onERC1155Received( address operator, address from, uint256 id, uint256 value, bytes calldata data ) external returns (bytes4); /** * @dev Handles the receipt of a multiple ERC1155 token types. This function * is called at the end of a `safeBatchTransferFrom` after the balances have * been updated. * * NOTE: To accept the transfer(s), this must return * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` * (i.e. 0xbc197c81, or its own function selector). * * @param operator The address which initiated the batch transfer (i.e. msg.sender) * @param from The address which previously owned the token * @param ids An array containing ids of each token being transferred (order and length must match values array) * @param values An array containing amounts of each token being transferred (order and length must match ids array) * @param data Additional data with no specified format * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed */ function onERC1155BatchReceived( address operator, address from, uint256[] calldata ids, uint256[] calldata values, bytes calldata data ) external returns (bytes4); }
// 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); }
{ "remappings": [ "@solmate/=lib/solmate/src/", "@forge-std/=lib/forge-std/src/", "@ds-test/=lib/forge-std/lib/ds-test/src/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@openzeppelin/=lib/openzeppelin-contracts/", "@ccip/=lib/ccip/", "@oapp-auth/=lib/OAppAuth/src/", "@devtools-oapp-evm/=lib/OAppAuth/lib/devtools/packages/oapp-evm/contracts/oapp/", "@layerzerolabs/lz-evm-messagelib-v2/=lib/OAppAuth/node_modules/@layerzerolabs/lz-evm-messagelib-v2/", "@layerzerolabs/lz-evm-protocol-v2/=lib/OAppAuth/lib/LayerZero-V2/packages/layerzero-v2/evm/protocol/", "@layerzerolabs/oapp-evm/=lib/OAppAuth/lib/devtools/packages/oapp-evm/", "@lz-oapp-evm/=lib/OAppAuth/lib/LayerZero-V2/packages/layerzero-v2/evm/oapp/contracts/oapp/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@sbu/=lib/OAppAuth/lib/solidity-bytes-utils/", "LayerZero-V2/=lib/OAppAuth/lib/", "OAppAuth/=lib/OAppAuth/", "ccip/=lib/ccip/contracts/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "forge-std/=lib/forge-std/src/", "halmos-cheatcodes/=lib/OAppAuth/lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "solidity-bytes-utils/=lib/OAppAuth/node_modules/solidity-bytes-utils/", "solmate/=lib/solmate/src/" ], "optimizer": { "enabled": true, "runs": 200 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "shanghai", "viaIR": false, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_auth","type":"address"},{"internalType":"address","name":"_queue","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"boringVault","type":"address"},{"internalType":"address","name":"teller","type":"address"}],"name":"BoringSolver___BoringVaultTellerMismatch","type":"error"},{"inputs":[],"name":"BoringSolver___FailedToSolve","type":"error"},{"inputs":[],"name":"BoringSolver___OnlyQueue","type":"error"},{"inputs":[],"name":"BoringSolver___OnlySelf","type":"error"},{"inputs":[],"name":"BoringSolver___WrongInitiator","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"contract Authority","name":"newAuthority","type":"address"}],"name":"AuthorityUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"authority","outputs":[{"internalType":"contract Authority","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"nonce","type":"uint96"},{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"assetOut","type":"address"},{"internalType":"uint128","name":"amountOfShares","type":"uint128"},{"internalType":"uint128","name":"amountOfAssets","type":"uint128"},{"internalType":"uint40","name":"creationTime","type":"uint40"},{"internalType":"uint24","name":"secondsToMaturity","type":"uint24"},{"internalType":"uint24","name":"secondsToDeadline","type":"uint24"}],"internalType":"struct BoringOnChainQueue.OnChainWithdraw","name":"request","type":"tuple"},{"internalType":"address","name":"fromTeller","type":"address"},{"internalType":"address","name":"toTeller","type":"address"},{"internalType":"address","name":"intermediateAsset","type":"address"}],"name":"boringRedeemMintSelfSolve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"nonce","type":"uint96"},{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"assetOut","type":"address"},{"internalType":"uint128","name":"amountOfShares","type":"uint128"},{"internalType":"uint128","name":"amountOfAssets","type":"uint128"},{"internalType":"uint40","name":"creationTime","type":"uint40"},{"internalType":"uint24","name":"secondsToMaturity","type":"uint24"},{"internalType":"uint24","name":"secondsToDeadline","type":"uint24"}],"internalType":"struct BoringOnChainQueue.OnChainWithdraw[]","name":"requests","type":"tuple[]"},{"internalType":"address","name":"fromTeller","type":"address"},{"internalType":"address","name":"toTeller","type":"address"},{"internalType":"address","name":"intermediateAsset","type":"address"}],"name":"boringRedeemMintSolve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"nonce","type":"uint96"},{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"assetOut","type":"address"},{"internalType":"uint128","name":"amountOfShares","type":"uint128"},{"internalType":"uint128","name":"amountOfAssets","type":"uint128"},{"internalType":"uint40","name":"creationTime","type":"uint40"},{"internalType":"uint24","name":"secondsToMaturity","type":"uint24"},{"internalType":"uint24","name":"secondsToDeadline","type":"uint24"}],"internalType":"struct BoringOnChainQueue.OnChainWithdraw","name":"request","type":"tuple"},{"internalType":"address","name":"teller","type":"address"}],"name":"boringRedeemSelfSolve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"nonce","type":"uint96"},{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"assetOut","type":"address"},{"internalType":"uint128","name":"amountOfShares","type":"uint128"},{"internalType":"uint128","name":"amountOfAssets","type":"uint128"},{"internalType":"uint40","name":"creationTime","type":"uint40"},{"internalType":"uint24","name":"secondsToMaturity","type":"uint24"},{"internalType":"uint24","name":"secondsToDeadline","type":"uint24"}],"internalType":"struct BoringOnChainQueue.OnChainWithdraw[]","name":"requests","type":"tuple[]"},{"internalType":"address","name":"teller","type":"address"}],"name":"boringRedeemSolve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"initiator","type":"address"},{"internalType":"address","name":"boringVault","type":"address"},{"internalType":"address","name":"solveAsset","type":"address"},{"internalType":"uint256","name":"totalShares","type":"uint256"},{"internalType":"uint256","name":"requiredAssets","type":"uint256"},{"internalType":"bytes","name":"solveData","type":"bytes"}],"name":"boringSolve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract Authority","name":"newAuthority","type":"address"}],"name":"setAuthority","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
60a060405234801562000010575f80fd5b5060405162002001380380620020018339810160408190526200003391620000f5565b5f80546001600160a01b03199081166001600160a01b0386811691821784556001805490931690861617909155604051859285929133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a36040516001600160a01b0382169033907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350506001600160a01b0316608052506200013c9050565b80516001600160a01b0381168114620000f0575f80fd5b919050565b5f805f6060848603121562000108575f80fd5b6200011384620000d9565b92506200012360208501620000d9565b91506200013360408501620000d9565b90509250925092565b608051611e7b620001865f395f81816102a501528181610497015281816106e9015281816108ba015281816109e601528181610ccb01528181610ffc01526111620152611e7b5ff3fe608060405234801561000f575f80fd5b50600436106100a6575f3560e01c80638f3866081161006e5780638f38660814610127578063ac9650d81461013a578063b7532db21461015a578063bf7e214f1461016d578063f2fde38b14610180578063ff011b6214610193575f80fd5b806357376198146100aa57806367aa0416146100bf57806372faf4a4146100d25780637a9e5e4b146100e55780638da5cb5b146100f8575b5f80fd5b6100bd6100b83660046113dd565b6101a6565b005b6100bd6100cd366004611417565b610269565b6100bd6100e03660046114e3565b610390565b6100bd6100f336600461151b565b6104f9565b5f5461010a906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100bd610135366004611536565b6105dd565b61014d610148366004611592565b610757565b60405161011e919061164e565b6100bd6101683660046116f6565b610849565b60015461010a906001600160a01b031681565b6100bd61018e36600461151b565b6108f5565b6100bd6101a1366004611749565b610970565b6101bb335f356001600160e01b031916610a21565b6101e05760405162461bcd60e51b81526004016101d7906117c1565b60405180910390fd5b5f198103610251576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561022a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061024e91906117e7565b90505b6102656001600160a01b0383163383610ac7565b5050565b61027e335f356001600160e01b031916610a21565b61029a5760405162461bcd60e51b81526004016101d7906117c1565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102e3576040516337aab0fd60e11b815260040160405180910390fd5b6001600160a01b038716301461030c5760405163702093cb60e11b815260040160405180910390fd5b5f6103198284018461180c565b90505f81600181111561032e5761032e611825565b0361034657610341838389898989610b4a565b610386565b600181600181111561035a5761035a611825565b0361036d57610341838389898989610d52565b6040516336ad3b5560e21b815260040160405180910390fd5b5050505050505050565b6103a5335f356001600160e01b031916610a21565b6103c15760405162461bcd60e51b81526004016101d7906117c1565b336103d2604084016020850161151b565b6001600160a01b0316146103f9576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b610417611386565b81526020019060019003908161040f57905050905061043b3684900384018461189f565b815f8151811061044d5761044d61196e565b60200260200101819052505f8033845f60405160200161047094939291906119a2565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc906104d0908590859030906004016119d9565b5f604051808303815f87803b1580156104e7575f80fd5b505af1158015610386573d5f803e3d5ffd5b5f546001600160a01b031633148061058a575060015460405163b700961360e01b81526001600160a01b039091169063b70096139061054b90339030906001600160e01b03195f351690600401611abb565b602060405180830381865afa158015610566573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061058a9190611af5565b610592575f80fd5b600180546001600160a01b0319166001600160a01b03831690811790915560405133907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350565b6105f2335f356001600160e01b031916610a21565b61060e5760405162461bcd60e51b81526004016101d7906117c1565b3361061f604086016020870161151b565b6001600160a01b031614610646576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b610664611386565b81526020019060019003908161065c5790505090506106883686900386018661189f565b815f8151811061069a5761069a61196e565b60200260200101819052505f6001338686865f6040516020016106c296959493929190611b10565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc90610722908590859030906004016119d9565b5f604051808303815f87803b158015610739575f80fd5b505af115801561074b573d5f803e3d5ffd5b50505050505050505050565b604080515f8152602081019091526060908267ffffffffffffffff81111561078157610781611839565b6040519080825280602002602001820160405280156107b457816020015b606081526020019060019003908161079f5790505b5091505f5b8381101561084057610810308686848181106107d7576107d761196e565b90506020028101906107e99190611b7d565b856040516020016107fc93929190611bc0565b6040516020818303038152906040526111ea565b8382815181106108225761082261196e565b6020026020010181905250808061083890611be5565b9150506107b9565b50505b92915050565b61085e335f356001600160e01b031916610a21565b61087a5760405162461bcd60e51b81526004016101d7906117c1565b5f803383600160405160200161089394939291906119a2565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc906104d0908790879086903090600401611bfd565b61090a335f356001600160e01b031916610a21565b6109265760405162461bcd60e51b81526004016101d7906117c1565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b610985335f356001600160e01b031916610a21565b6109a15760405162461bcd60e51b81526004016101d7906117c1565b5f60013385858560016040516020016109bf96959493929190611b10565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc90610722908990899086903090600401611bfd565b6001545f906001600160a01b03168015801590610aa8575060405163b700961360e01b81526001600160a01b0382169063b700961390610a6990879030908890600401611abb565b602060405180830381865afa158015610a84573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610aa89190611af5565b80610abf57505f546001600160a01b038581169116145b949350505050565b5f60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610b445760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b60448201526064016101d7565b50505050565b5f8080610b59888a018a611d1c565b93509350935050816001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b9c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bc09190611d68565b6001600160a01b0316876001600160a01b031614610c0457604051631469fe1360e21b81526001600160a01b038089166004830152831660248201526044016101d7565b604051633e64ce9960e01b815286905f906001600160a01b03851690633e64ce9990610c3a9085908b908b903090600401611d83565b6020604051808303815f875af1158015610c56573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c7a91906117e7565b90508215610ca657610ca185610c908884611b6a565b6001600160a01b0385169190610ac7565b610cb4565b610cb489610c908884611b6a565b60405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301526024820188905283169063095ea7b3906044016020604051808303815f875af1158015610d20573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d449190611af5565b505050505050505050505050565b5f80808080610d638a8c018c611dae565b9550955095509550955050836001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610daa573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610dce9190611d68565b6001600160a01b0316896001600160a01b031614610e1257604051631469fe1360e21b81526001600160a01b03808b166004830152851660248201526044016101d7565b826001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e4e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e729190611d68565b6001600160a01b0316886001600160a01b031614610eb657604051631469fe1360e21b81526001600160a01b03808a166004830152841660248201526044016101d7565b604051633e64ce9960e01b81525f906001600160a01b03861690633e64ce9990610eea9086908c9086903090600401611d83565b6020604051808303815f875af1158015610f06573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f2a91906117e7565b90505f611082856001600160a01b0316634fb3ccc56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f909190611d68565b604051634104b9ed60e11b81526001600160a01b038781166004830152919091169063820973da90602401602060405180830381865afa158015610fd6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ffa91906117e7565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b7d122b56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611056573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061107a91906117e7565b8a919061125c565b905061108e8183611b6a565b91506110a46001600160a01b0385168b8361127f565b6040516304eaba2160e51b81526001600160a01b03861690639d574420906110d690879085908d903090600401611d83565b6020604051808303815f875af11580156110f2573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061111691906117e7565b50508115611137576111326001600160a01b0384168783610ac7565b61114b565b61114b6001600160a01b0384168b83610ac7565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018990528a169063095ea7b3906044016020604051808303815f875af11580156111b7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111db9190611af5565b50505050505050505050505050565b60605f80846001600160a01b0316846040516112069190611e2a565b5f60405180830381855af49150503d805f811461123e576040519150601f19603f3d011682016040523d82523d5f602084013e611243565b606091505b50915091506112538583836112fb565b95945050505050565b5f825f190484118302158202611270575f80fd5b50910281810615159190040190565b5f60405163095ea7b360e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610b445760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b60448201526064016101d7565b6060826113105761130b8261135a565b611353565b815115801561132757506001600160a01b0384163b155b1561135057604051639996b31560e01b81526001600160a01b03851660048201526024016101d7565b50805b9392505050565b80511561136a5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915290565b6001600160a01b0381168114611383575f80fd5b5f80604083850312156113ee575f80fd5b82356113f9816113c9565b946020939093013593505050565b8035611412816113c9565b919050565b5f805f805f805f60c0888a03121561142d575f80fd5b8735611438816113c9565b96506020880135611448816113c9565b95506040880135611458816113c9565b9450606088013593506080880135925060a088013567ffffffffffffffff80821115611482575f80fd5b818a0191508a601f830112611495575f80fd5b8135818111156114a3575f80fd5b8b60208285010111156114b4575f80fd5b60208301945080935050505092959891949750929550565b5f61010082840312156114dd575f80fd5b50919050565b5f8061012083850312156114f5575f80fd5b6114ff84846114cc565b9150610100830135611510816113c9565b809150509250929050565b5f6020828403121561152b575f80fd5b8135611353816113c9565b5f805f80610160858703121561154a575f80fd5b61155486866114cc565b9350610100850135611565816113c9565b9250610120850135611576816113c9565b9150610140850135611587816113c9565b939692955090935050565b5f80602083850312156115a3575f80fd5b823567ffffffffffffffff808211156115ba575f80fd5b818501915085601f8301126115cd575f80fd5b8135818111156115db575f80fd5b8660208260051b85010111156115ef575f80fd5b60209290920196919550909350505050565b5f5b8381101561161b578181015183820152602001611603565b50505f910152565b5f815180845261163a816020860160208601611601565b601f01601f19169290920160200192915050565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156116a157603f1988860301845261168f858351611623565b94509285019290850190600101611673565b5092979650505050505050565b5f8083601f8401126116be575f80fd5b50813567ffffffffffffffff8111156116d5575f80fd5b6020830191508360208260081b85010111156116ef575f80fd5b9250929050565b5f805f60408486031215611708575f80fd5b833567ffffffffffffffff81111561171e575f80fd5b61172a868287016116ae565b909450925050602084013561173e816113c9565b809150509250925092565b5f805f805f6080868803121561175d575f80fd5b853567ffffffffffffffff811115611773575f80fd5b61177f888289016116ae565b9096509450506020860135611793816113c9565b925060408601356117a3816113c9565b915060608601356117b3816113c9565b809150509295509295909350565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b5f602082840312156117f7575f80fd5b5051919050565b803560028110611412575f80fd5b5f6020828403121561181c575f80fd5b611353826117fe565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b80356001600160601b0381168114611412575f80fd5b80356001600160801b0381168114611412575f80fd5b803564ffffffffff81168114611412575f80fd5b803562ffffff81168114611412575f80fd5b5f6101008083850312156118b1575f80fd5b6040519081019067ffffffffffffffff821181831017156118e057634e487b7160e01b5f52604160045260245ffd5b816040526118ed8461184d565b81526118fb60208501611407565b602082015261190c60408501611407565b604082015261191d60608501611863565b606082015261192e60808501611863565b608082015261193f60a08501611879565b60a082015261195060c0850161188d565b60c082015261196160e0850161188d565b60e0820152949350505050565b634e487b7160e01b5f52603260045260245ffd5b6002811061199e57634e487b7160e01b5f52602160045260245ffd5b9052565b608081016119b08287611982565b6001600160a01b0394851660208301529290931660408401521515606090920191909152919050565b606080825284518282018190525f9190608090818501906020808a01865b83811015611a8d57815180516001600160601b03168652838101516001600160a01b039081168588015260408083015190911690870152878101516001600160801b039081168988015287820151168787015260a08082015164ffffffffff169087015260c08082015162ffffff9081169188019190915260e091820151169086015261010090940193908201906001016119f7565b50508683039087015250611aa18188611623565b9350505050610abf60408301846001600160a01b03169052565b6001600160a01b0393841681529190921660208201526001600160e01b0319909116604082015260600190565b8015158114611383575f80fd5b5f60208284031215611b05575f80fd5b815161135381611ae8565b60c08101611b1e8289611982565b6001600160a01b039687166020830152948616604082015292851660608401529316608082015291151560a090920191909152919050565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561084357610843611b56565b5f808335601e19843603018112611b92575f80fd5b83018035915067ffffffffffffffff821115611bac575f80fd5b6020019150368190038213156116ef575f80fd5b828482375f8382015f81528351611bdb818360208801611601565b0195945050505050565b5f60018201611bf657611bf6611b56565b5060010190565b60608082528181018590525f90608080840188845b89811015611cee576001600160601b03611c2b8361184d565b168352602080830135611c3d816113c9565b6001600160a01b031690840152604082810135611c59816113c9565b6001600160a01b031690840152611c71828601611863565b6001600160801b031685840152611c89828501611863565b6001600160801b03168484015260a0611ca3838201611879565b64ffffffffff169084015260c0611cbb83820161188d565b62ffffff169084015260e0611cd183820161188d565b62ffffff1690840152610100928301929190910190600101611c12565b50508481036020860152611d028188611623565b935050505061125360408301846001600160a01b03169052565b5f805f8060808587031215611d2f575f80fd5b611d38856117fe565b93506020850135611d48816113c9565b92506040850135611d58816113c9565b9150606085013561158781611ae8565b5f60208284031215611d78575f80fd5b8151611353816113c9565b6001600160a01b03948516815260208101939093526040830191909152909116606082015260800190565b5f805f805f8060c08789031215611dc3575f80fd5b611dcc876117fe565b95506020870135611ddc816113c9565b94506040870135611dec816113c9565b93506060870135611dfc816113c9565b92506080870135611e0c816113c9565b915060a0870135611e1c81611ae8565b809150509295509295509295565b5f8251611e3b818460208701611601565b919091019291505056fea2646970667358221220a5741c30db9edacf86cae8f15d1e1379049a9d49d8cafc6c4b676da214fe245a64736f6c634300081500330000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133
Deployed Bytecode
0x608060405234801561000f575f80fd5b50600436106100a6575f3560e01c80638f3866081161006e5780638f38660814610127578063ac9650d81461013a578063b7532db21461015a578063bf7e214f1461016d578063f2fde38b14610180578063ff011b6214610193575f80fd5b806357376198146100aa57806367aa0416146100bf57806372faf4a4146100d25780637a9e5e4b146100e55780638da5cb5b146100f8575b5f80fd5b6100bd6100b83660046113dd565b6101a6565b005b6100bd6100cd366004611417565b610269565b6100bd6100e03660046114e3565b610390565b6100bd6100f336600461151b565b6104f9565b5f5461010a906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100bd610135366004611536565b6105dd565b61014d610148366004611592565b610757565b60405161011e919061164e565b6100bd6101683660046116f6565b610849565b60015461010a906001600160a01b031681565b6100bd61018e36600461151b565b6108f5565b6100bd6101a1366004611749565b610970565b6101bb335f356001600160e01b031916610a21565b6101e05760405162461bcd60e51b81526004016101d7906117c1565b60405180910390fd5b5f198103610251576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561022a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061024e91906117e7565b90505b6102656001600160a01b0383163383610ac7565b5050565b61027e335f356001600160e01b031916610a21565b61029a5760405162461bcd60e51b81526004016101d7906117c1565b336001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e13316146102e3576040516337aab0fd60e11b815260040160405180910390fd5b6001600160a01b038716301461030c5760405163702093cb60e11b815260040160405180910390fd5b5f6103198284018461180c565b90505f81600181111561032e5761032e611825565b0361034657610341838389898989610b4a565b610386565b600181600181111561035a5761035a611825565b0361036d57610341838389898989610d52565b6040516336ad3b5560e21b815260040160405180910390fd5b5050505050505050565b6103a5335f356001600160e01b031916610a21565b6103c15760405162461bcd60e51b81526004016101d7906117c1565b336103d2604084016020850161151b565b6001600160a01b0316146103f9576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b610417611386565b81526020019060019003908161040f57905050905061043b3684900384018461189f565b815f8151811061044d5761044d61196e565b60200260200101819052505f8033845f60405160200161047094939291906119a2565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133169063412638dc906104d0908590859030906004016119d9565b5f604051808303815f87803b1580156104e7575f80fd5b505af1158015610386573d5f803e3d5ffd5b5f546001600160a01b031633148061058a575060015460405163b700961360e01b81526001600160a01b039091169063b70096139061054b90339030906001600160e01b03195f351690600401611abb565b602060405180830381865afa158015610566573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061058a9190611af5565b610592575f80fd5b600180546001600160a01b0319166001600160a01b03831690811790915560405133907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350565b6105f2335f356001600160e01b031916610a21565b61060e5760405162461bcd60e51b81526004016101d7906117c1565b3361061f604086016020870161151b565b6001600160a01b031614610646576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b610664611386565b81526020019060019003908161065c5790505090506106883686900386018661189f565b815f8151811061069a5761069a61196e565b60200260200101819052505f6001338686865f6040516020016106c296959493929190611b10565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133169063412638dc90610722908590859030906004016119d9565b5f604051808303815f87803b158015610739575f80fd5b505af115801561074b573d5f803e3d5ffd5b50505050505050505050565b604080515f8152602081019091526060908267ffffffffffffffff81111561078157610781611839565b6040519080825280602002602001820160405280156107b457816020015b606081526020019060019003908161079f5790505b5091505f5b8381101561084057610810308686848181106107d7576107d761196e565b90506020028101906107e99190611b7d565b856040516020016107fc93929190611bc0565b6040516020818303038152906040526111ea565b8382815181106108225761082261196e565b6020026020010181905250808061083890611be5565b9150506107b9565b50505b92915050565b61085e335f356001600160e01b031916610a21565b61087a5760405162461bcd60e51b81526004016101d7906117c1565b5f803383600160405160200161089394939291906119a2565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133169063412638dc906104d0908790879086903090600401611bfd565b61090a335f356001600160e01b031916610a21565b6109265760405162461bcd60e51b81526004016101d7906117c1565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b610985335f356001600160e01b031916610a21565b6109a15760405162461bcd60e51b81526004016101d7906117c1565b5f60013385858560016040516020016109bf96959493929190611b10565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133169063412638dc90610722908990899086903090600401611bfd565b6001545f906001600160a01b03168015801590610aa8575060405163b700961360e01b81526001600160a01b0382169063b700961390610a6990879030908890600401611abb565b602060405180830381865afa158015610a84573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610aa89190611af5565b80610abf57505f546001600160a01b038581169116145b949350505050565b5f60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610b445760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b60448201526064016101d7565b50505050565b5f8080610b59888a018a611d1c565b93509350935050816001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b9c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bc09190611d68565b6001600160a01b0316876001600160a01b031614610c0457604051631469fe1360e21b81526001600160a01b038089166004830152831660248201526044016101d7565b604051633e64ce9960e01b815286905f906001600160a01b03851690633e64ce9990610c3a9085908b908b903090600401611d83565b6020604051808303815f875af1158015610c56573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c7a91906117e7565b90508215610ca657610ca185610c908884611b6a565b6001600160a01b0385169190610ac7565b610cb4565b610cb489610c908884611b6a565b60405163095ea7b360e01b81526001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133811660048301526024820188905283169063095ea7b3906044016020604051808303815f875af1158015610d20573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d449190611af5565b505050505050505050505050565b5f80808080610d638a8c018c611dae565b9550955095509550955050836001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610daa573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610dce9190611d68565b6001600160a01b0316896001600160a01b031614610e1257604051631469fe1360e21b81526001600160a01b03808b166004830152851660248201526044016101d7565b826001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e4e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e729190611d68565b6001600160a01b0316886001600160a01b031614610eb657604051631469fe1360e21b81526001600160a01b03808a166004830152841660248201526044016101d7565b604051633e64ce9960e01b81525f906001600160a01b03861690633e64ce9990610eea9086908c9086903090600401611d83565b6020604051808303815f875af1158015610f06573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f2a91906117e7565b90505f611082856001600160a01b0316634fb3ccc56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f909190611d68565b604051634104b9ed60e11b81526001600160a01b038781166004830152919091169063820973da90602401602060405180830381865afa158015610fd6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ffa91906117e7565b7f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e1336001600160a01b031663b7d122b56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611056573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061107a91906117e7565b8a919061125c565b905061108e8183611b6a565b91506110a46001600160a01b0385168b8361127f565b6040516304eaba2160e51b81526001600160a01b03861690639d574420906110d690879085908d903090600401611d83565b6020604051808303815f875af11580156110f2573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061111691906117e7565b50508115611137576111326001600160a01b0384168783610ac7565b61114b565b61114b6001600160a01b0384168b83610ac7565b60405163095ea7b360e01b81526001600160a01b037f00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e13381166004830152602482018990528a169063095ea7b3906044016020604051808303815f875af11580156111b7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111db9190611af5565b50505050505050505050505050565b60605f80846001600160a01b0316846040516112069190611e2a565b5f60405180830381855af49150503d805f811461123e576040519150601f19603f3d011682016040523d82523d5f602084013e611243565b606091505b50915091506112538583836112fb565b95945050505050565b5f825f190484118302158202611270575f80fd5b50910281810615159190040190565b5f60405163095ea7b360e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610b445760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b60448201526064016101d7565b6060826113105761130b8261135a565b611353565b815115801561132757506001600160a01b0384163b155b1561135057604051639996b31560e01b81526001600160a01b03851660048201526024016101d7565b50805b9392505050565b80511561136a5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915290565b6001600160a01b0381168114611383575f80fd5b5f80604083850312156113ee575f80fd5b82356113f9816113c9565b946020939093013593505050565b8035611412816113c9565b919050565b5f805f805f805f60c0888a03121561142d575f80fd5b8735611438816113c9565b96506020880135611448816113c9565b95506040880135611458816113c9565b9450606088013593506080880135925060a088013567ffffffffffffffff80821115611482575f80fd5b818a0191508a601f830112611495575f80fd5b8135818111156114a3575f80fd5b8b60208285010111156114b4575f80fd5b60208301945080935050505092959891949750929550565b5f61010082840312156114dd575f80fd5b50919050565b5f8061012083850312156114f5575f80fd5b6114ff84846114cc565b9150610100830135611510816113c9565b809150509250929050565b5f6020828403121561152b575f80fd5b8135611353816113c9565b5f805f80610160858703121561154a575f80fd5b61155486866114cc565b9350610100850135611565816113c9565b9250610120850135611576816113c9565b9150610140850135611587816113c9565b939692955090935050565b5f80602083850312156115a3575f80fd5b823567ffffffffffffffff808211156115ba575f80fd5b818501915085601f8301126115cd575f80fd5b8135818111156115db575f80fd5b8660208260051b85010111156115ef575f80fd5b60209290920196919550909350505050565b5f5b8381101561161b578181015183820152602001611603565b50505f910152565b5f815180845261163a816020860160208601611601565b601f01601f19169290920160200192915050565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156116a157603f1988860301845261168f858351611623565b94509285019290850190600101611673565b5092979650505050505050565b5f8083601f8401126116be575f80fd5b50813567ffffffffffffffff8111156116d5575f80fd5b6020830191508360208260081b85010111156116ef575f80fd5b9250929050565b5f805f60408486031215611708575f80fd5b833567ffffffffffffffff81111561171e575f80fd5b61172a868287016116ae565b909450925050602084013561173e816113c9565b809150509250925092565b5f805f805f6080868803121561175d575f80fd5b853567ffffffffffffffff811115611773575f80fd5b61177f888289016116ae565b9096509450506020860135611793816113c9565b925060408601356117a3816113c9565b915060608601356117b3816113c9565b809150509295509295909350565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b5f602082840312156117f7575f80fd5b5051919050565b803560028110611412575f80fd5b5f6020828403121561181c575f80fd5b611353826117fe565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b80356001600160601b0381168114611412575f80fd5b80356001600160801b0381168114611412575f80fd5b803564ffffffffff81168114611412575f80fd5b803562ffffff81168114611412575f80fd5b5f6101008083850312156118b1575f80fd5b6040519081019067ffffffffffffffff821181831017156118e057634e487b7160e01b5f52604160045260245ffd5b816040526118ed8461184d565b81526118fb60208501611407565b602082015261190c60408501611407565b604082015261191d60608501611863565b606082015261192e60808501611863565b608082015261193f60a08501611879565b60a082015261195060c0850161188d565b60c082015261196160e0850161188d565b60e0820152949350505050565b634e487b7160e01b5f52603260045260245ffd5b6002811061199e57634e487b7160e01b5f52602160045260245ffd5b9052565b608081016119b08287611982565b6001600160a01b0394851660208301529290931660408401521515606090920191909152919050565b606080825284518282018190525f9190608090818501906020808a01865b83811015611a8d57815180516001600160601b03168652838101516001600160a01b039081168588015260408083015190911690870152878101516001600160801b039081168988015287820151168787015260a08082015164ffffffffff169087015260c08082015162ffffff9081169188019190915260e091820151169086015261010090940193908201906001016119f7565b50508683039087015250611aa18188611623565b9350505050610abf60408301846001600160a01b03169052565b6001600160a01b0393841681529190921660208201526001600160e01b0319909116604082015260600190565b8015158114611383575f80fd5b5f60208284031215611b05575f80fd5b815161135381611ae8565b60c08101611b1e8289611982565b6001600160a01b039687166020830152948616604082015292851660608401529316608082015291151560a090920191909152919050565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561084357610843611b56565b5f808335601e19843603018112611b92575f80fd5b83018035915067ffffffffffffffff821115611bac575f80fd5b6020019150368190038213156116ef575f80fd5b828482375f8382015f81528351611bdb818360208801611601565b0195945050505050565b5f60018201611bf657611bf6611b56565b5060010190565b60608082528181018590525f90608080840188845b89811015611cee576001600160601b03611c2b8361184d565b168352602080830135611c3d816113c9565b6001600160a01b031690840152604082810135611c59816113c9565b6001600160a01b031690840152611c71828601611863565b6001600160801b031685840152611c89828501611863565b6001600160801b03168484015260a0611ca3838201611879565b64ffffffffff169084015260c0611cbb83820161188d565b62ffffff169084015260e0611cd183820161188d565b62ffffff1690840152610100928301929190910190600101611c12565b50508481036020860152611d028188611623565b935050505061125360408301846001600160a01b03169052565b5f805f8060808587031215611d2f575f80fd5b611d38856117fe565b93506020850135611d48816113c9565b92506040850135611d58816113c9565b9150606085013561158781611ae8565b5f60208284031215611d78575f80fd5b8151611353816113c9565b6001600160a01b03948516815260208101939093526040830191909152909116606082015260800190565b5f805f805f8060c08789031215611dc3575f80fd5b611dcc876117fe565b95506020870135611ddc816113c9565b94506040870135611dec816113c9565b93506060870135611dfc816113c9565b92506080870135611e0c816113c9565b915060a0870135611e1c81611ae8565b809150509295509295509295565b5f8251611e3b818460208701611601565b919091019291505056fea2646970667358221220a5741c30db9edacf86cae8f15d1e1379049a9d49d8cafc6c4b676da214fe245a64736f6c63430008150033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133
-----Decoded View---------------
Arg [0] : _owner (address): 0x5F2F11ad8656439d5C14d9B351f8b09cDaC2A02d
Arg [1] : _auth (address): 0x0000000000000000000000000000000000000000
Arg [2] : _queue (address): 0x65b6AFB8C1521B48488dF04224Dc019Ea390E133
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [2] : 00000000000000000000000065b6afb8c1521b48488df04224dc019ea390e133
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ 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.