Source Code
Overview
ETH Balance
0 ETH
ETH Value
$0.00| Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 5432796 | 130 days ago | Contract Creation | 0 ETH |
Cross-Chain Transactions
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
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();
error BoringSolver___CannotCoverDeficit(uint256 deficit);
//============================== IMMUTABLES ===============================
BoringOnChainQueue internal immutable queue;
/**
* @notice Whether to send excess assets to the solver or the Boring Vault on non-self solves.
*/
bool public immutable excessToSolverNonSelfSolve;
constructor(address _owner, address _auth, address _queue, bool _excessToSolverNonSelfSolve)
Auth(_owner, Authority(_auth))
{
queue = BoringOnChainQueue(_queue);
excessToSolverNonSelfSolve = _excessToSolverNonSelfSolve;
}
//============================== 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,
bool coverDeficit
) external requiresAuth {
bytes memory solveData =
abi.encode(SolveType.BORING_REDEEM, msg.sender, teller, excessToSolverNonSelfSolve, coverDeficit);
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,
bool coverDeficit
) external requiresAuth {
bytes memory solveData = abi.encode(
SolveType.BORING_REDEEM_MINT,
msg.sender,
fromTeller,
toTeller,
intermediateAsset,
excessToSolverNonSelfSolve,
coverDeficit
);
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, 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, 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, bool coverDeficit) =
abi.decode(solveData, (SolveType, address, TellerWithMultiAssetSupport, bool, 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, 0, address(this));
if (assetsOut > requiredAssets) {
// 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);
}
} else if (assetsOut < requiredAssets) {
// We have a deficit, cover it using solver origin funds if allowed.
uint256 deficit = requiredAssets - assetsOut;
if (coverDeficit) {
asset.safeTransferFrom(solverOrigin, address(this), deficit);
} else {
revert BoringSolver___CannotCoverDeficit(deficit);
}
} // else nothing to do, we have exact change.
// Approve Boring Queue to spend the required assets.
asset.safeApprove(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,
bool coverDeficit
) = abi.decode(
solveData, (SolveType, address, TellerWithMultiAssetSupport, TellerWithMultiAssetSupport, ERC20, bool, 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()
);
if (excessAssets > assetsToMintRequiredShares) {
// Remove assetsToMintRequiredShares from excessAssets.
excessAssets = excessAssets - assetsToMintRequiredShares;
} else if (excessAssets < assetsToMintRequiredShares) {
// We have a deficit, cover it using solver origin funds if allowed.
uint256 deficit = assetsToMintRequiredShares - excessAssets;
if (coverDeficit) {
intermediateAsset.safeTransferFrom(solverOrigin, address(this), deficit);
} else {
revert BoringSolver___CannotCoverDeficit(deficit);
}
excessAssets = 0;
} else {
excessAssets = 0;
}
// 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 (excessAssets > 0) {
if (excessToSolver) {
intermediateAsset.safeTransfer(solverOrigin, excessAssets);
} else {
intermediateAsset.safeTransfer(fromBoringVault, excessAssets);
}
}
// Approve Boring Queue to spend the required assets.
ERC20(toBoringVault).safeApprove(address(queue), requiredShares);
}
}// 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) (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: 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) (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) (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.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: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/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: 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: 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: 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: 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: 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;
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: 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.
* @param withdrawCapacity The maximum amount of total shares that can be withdrawn.
* - Can be set to type(uint256).max to allow unlimited withdraws.
* - Decremented when users make requests.
* - Incremented when users cancel requests.
* - Can be set by admin.
*/
struct WithdrawAsset {
bool allowWithdraws;
uint24 secondsToMaturity;
uint24 minimumSecondsToDeadline;
uint16 minDiscount;
uint16 maxDiscount;
uint96 minimumShares;
uint256 withdrawCapacity;
}
/**
* @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();
error BoringOnChainQueue__NotEnoughWithdrawCapacity();
//============================== 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 WithdrawAssetStopped(address indexed assetOut);
event WithdrawAssetUpdated(
address indexed assetOut,
uint24 secondsToMaturity,
uint24 minimumSecondsToDeadline,
uint16 minDiscount,
uint16 maxDiscount,
uint96 minimumShares
);
event WithdrawCapacityUpdated(address indexed assetOut, uint256 withdrawCapacity);
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,
withdrawCapacity: type(uint256).max
});
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 Set the withdraw capacity for an asset.
* @dev Callable by STRATEGIST_MULTISIG_ROLE.
* @param assetOut The asset to set the withdraw capacity for.
* @param withdrawCapacity The new withdraw capacity.
*/
function setWithdrawCapacity(address assetOut, uint256 withdrawCapacity) external requiresAuth {
withdrawAssets[assetOut].withdrawCapacity = withdrawCapacity;
emit WithdrawCapacityUpdated(assetOut, withdrawCapacity);
}
/**
* @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)
{
_decrementWithdrawCapacity(assetOut, amountOfShares);
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) {
_decrementWithdrawCapacity(assetOut, amountOfShares);
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) = _replaceOnChainWithdraw(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);
_incrementWithdrawCapacity(request.assetOut, request.amountOfShares);
boringVault.safeTransfer(request.user, request.amountOfShares);
emit OnChainWithdrawCancelled(requestId, request.user, block.timestamp);
}
/**
* @notice Replace an on-chain withdraw.
* @dev Does not check withdraw capacity since it is replacing an existing request.
* @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 Decrement the withdraw capacity for an asset.
* @param assetOut The asset to decrement the withdraw capacity for.
* @param amountOfShares The amount of shares to decrement the withdraw capacity for.
*/
function _decrementWithdrawCapacity(address assetOut, uint256 amountOfShares) internal {
WithdrawAsset storage withdrawAsset = withdrawAssets[assetOut];
if (withdrawAsset.withdrawCapacity < type(uint256).max) {
if (withdrawAsset.withdrawCapacity < amountOfShares) revert BoringOnChainQueue__NotEnoughWithdrawCapacity();
withdrawAsset.withdrawCapacity -= amountOfShares;
emit WithdrawCapacityUpdated(assetOut, withdrawAsset.withdrawCapacity);
}
}
/**
* @notice Increment the withdraw capacity for an asset.
* @param assetOut The asset to increment the withdraw capacity for.
* @param amountOfShares The amount of shares to increment the withdraw capacity for.
*/
function _incrementWithdrawCapacity(address assetOut, uint256 amountOfShares) internal {
WithdrawAsset storage withdrawAsset = withdrawAssets[assetOut];
if (withdrawAsset.withdrawCapacity < type(uint256).max) {
withdrawAsset.withdrawCapacity += amountOfShares;
emit WithdrawCapacityUpdated(assetOut, withdrawAsset.withdrawCapacity);
}
}
/**
* @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: 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;
}
/**
* @param denyFrom bool indicating whether or not the user is on the deny from list.
* @param denyTo bool indicating whether or not the user is on the deny to list.
* @param denyOperator bool indicating whether or not the user is on the deny operator list.
* @param permissionedOperator bool indicating whether or not the user is a permissioned operator, only applies when permissionedTransfers is true.
* @param shareUnlockTime uint256 indicating the time at which the shares will be unlocked.
*/
struct BeforeTransferData {
bool denyFrom;
bool denyTo;
bool denyOperator;
bool permissionedOperator;
uint256 shareUnlockTime;
}
// ========================================= 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.
*/
uint64 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;
/**
* @notice If true, only permissioned operators can transfer shares.
*/
bool public permissionedTransfers;
/**
* @notice The global deposit cap of the vault.
* @dev If the cap is reached, no new deposits are accepted. No partial fills.
*/
uint112 public depositCap = type(uint112).max;
/**
* @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 address to BeforeTransferData struct to check if shares are locked and if the address is on any allow or deny list.
*/
mapping(address => BeforeTransferData) public beforeTransferData;
//============================== 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();
error TellerWithMultiAssetSupport__DepositExceedsCap();
//============================== 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);
event PermissionedTransfersSet(bool permissionedTransfers);
event AllowPermissionedOperator(address indexed operator);
event DenyPermissionedOperator(address indexed operator);
event DepositCapSet(uint112 cap);
// =============================== 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));
permissionedTransfers = false;
}
// ========================================= 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 {
beforeTransferData[user].denyFrom = true;
beforeTransferData[user].denyTo = true;
beforeTransferData[user].denyOperator = 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 {
beforeTransferData[user].denyFrom = false;
beforeTransferData[user].denyTo = false;
beforeTransferData[user].denyOperator = 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 {
beforeTransferData[user].denyFrom = 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 {
beforeTransferData[user].denyFrom = 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 {
beforeTransferData[user].denyTo = 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 {
beforeTransferData[user].denyTo = 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 {
beforeTransferData[user].denyOperator = 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 {
beforeTransferData[user].denyOperator = false;
emit AllowOperator(user);
}
/**
* @notice Set the permissioned transfers flag.
* @dev Callable by OWNER_ROLE.
*/
function setPermissionedTransfers(bool _permissionedTransfers) external requiresAuth {
permissionedTransfers = _permissionedTransfers;
emit PermissionedTransfersSet(_permissionedTransfers);
}
/**
* @notice Give permission to an operator to transfer shares when permissioned transfers flag is true.
* @dev Callable by OWNER_ROLE.
*/
function allowPermissionedOperator(address operator) external requiresAuth {
beforeTransferData[operator].permissionedOperator = true;
emit AllowPermissionedOperator(operator);
}
/**
* @notice Revoke permission from an operator to transfer shares when permissioned transfers flag is true.
* @dev Callable by OWNER_ROLE, and DENIER_.
*/
function denyPermissionedOperator(address operator) external requiresAuth {
beforeTransferData[operator].permissionedOperator = false;
emit DenyPermissionedOperator(operator);
}
/**
* @notice Set the deposit cap of the vault.
* @dev Callable by OWNER_ROLE
*/
function setDepositCap(uint112 cap) external requiresAuth {
depositCap = cap;
emit DepositCapSet(cap);
}
// ========================================= BeforeTransferHook FUNCTIONS =========================================
/**
* @notice Implement beforeTransfer hook to check if shares are locked, or if `from`, `to`, or `operator` are denied in beforeTransferData.
* @notice If permissionedTransfers is true, then only operators on the allow list can transfer shares.
* @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 (
beforeTransferData[from].denyFrom || beforeTransferData[to].denyTo
|| beforeTransferData[operator].denyOperator
|| (permissionedTransfers && !beforeTransferData[operator].permissionedOperator)
) {
revert TellerWithMultiAssetSupport__TransferDenied(from, to, operator);
}
if (beforeTransferData[from].shareUnlockTime > block.timestamp) {
revert TellerWithMultiAssetSupport__SharesAreLocked();
}
}
/**
* @notice Implement legacy beforeTransfer hook to check if shares are locked, or if `from`is on the deny list.
*/
function beforeTransfer(address from) public view virtual {
if (beforeTransferData[from].denyFrom) {
revert TellerWithMultiAssetSupport__TransferDenied(from, address(0), address(0));
}
if (beforeTransferData[from].shareUnlockTime > 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) {
uint112 cap = depositCap;
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();
if (cap != type(uint112).max) {
if (shares + vault.totalSupply() > cap) revert TellerWithMultiAssetSupport__DepositExceedsCap();
}
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) {
beforeTransferData[user].shareUnlockTime = 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: UNLICENSED
pragma solidity 0.8.21;
interface BeforeTransferHook {
function beforeTransfer(address from, address to, address operator) external view;
}// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;
interface IPausable {
function pause() external;
function unpause() external;
}// 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);
}{
"evmVersion": "shanghai",
"metadata": {
"appendCBOR": true,
"bytecodeHash": "ipfs",
"useLiteralContent": false
},
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
}
},
"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/"
],
"viaIR": false
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_auth","type":"address"},{"internalType":"address","name":"_queue","type":"address"},{"internalType":"bool","name":"_excessToSolverNonSelfSolve","type":"bool"}],"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":[{"internalType":"uint256","name":"deficit","type":"uint256"}],"name":"BoringSolver___CannotCoverDeficit","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"},{"internalType":"bool","name":"coverDeficit","type":"bool"}],"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"},{"internalType":"bool","name":"coverDeficit","type":"bool"}],"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":[],"name":"excessToSolverNonSelfSolve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","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
60c060405234801562000010575f80fd5b5060405162002214380380620022148339810160408190526200003391620000fc565b5f80546001600160a01b03199081166001600160a01b0387811691821784556001805490931690871617909155604051869286929133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a36040516001600160a01b0382169033907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350506001600160a01b03909116608052151560a052506200015b9050565b80516001600160a01b0381168114620000f7575f80fd5b919050565b5f805f806080858703121562000110575f80fd5b6200011b85620000e0565b93506200012b60208601620000e0565b92506200013b60408601620000e0565b91506060850151801515811462000150575f80fd5b939692955090935050565b60805160a051612059620001bb5f395f818160f5015281816102e201526109ca01525f818161033d015281816103e8015281816105dc0152818161083001528181610a2701528181610e13015281816110f301526112a101526120595ff3fe608060405234801561000f575f80fd5b50600436106100b1575f3560e01c80638da5cb5b1161006e5780638da5cb5b146101525780638f3866081461017c578063ac9650d81461018f578063bc9961f7146101af578063bf7e214f146101c2578063f2fde38b146101d5575f80fd5b806357376198146100b55780635ff8a71f146100ca57806367aa0416146100dd5780636b9f9fef146100f057806372faf4a41461012c5780637a9e5e4b1461013f575b5f80fd5b6100c86100c3366004611560565b6101e8565b005b6100c86100d83660046115ef565b6102ab565b6100c86100eb366004611653565b6103ac565b6101177f000000000000000000000000000000000000000000000000000000000000000081565b60405190151581526020015b60405180910390f35b6100c861013a36600461171f565b6104d3565b6100c861014d366004611757565b61063e565b5f54610164906001600160a01b031681565b6040516001600160a01b039091168152602001610123565b6100c861018a366004611772565b610722565b6101a261019d3660046117c3565b61089e565b604051610123919061187f565b6100c86101bd3660046118df565b610990565b600154610164906001600160a01b031681565b6100c86101e3366004611757565b610a98565b6101fd335f356001600160e01b031916610b13565b6102225760405162461bcd60e51b815260040161021990611968565b60405180910390fd5b5f198103610293576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561026c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610290919061198e565b90505b6102a76001600160a01b0383163383610bb9565b5050565b6102c0335f356001600160e01b031916610b13565b6102dc5760405162461bcd60e51b815260040161021990611968565b5f8033847f0000000000000000000000000000000000000000000000000000000000000000856040516020016103169594939291906119d9565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc90610378908890889086903090600401611a6a565b5f604051808303815f87803b15801561038f575f80fd5b505af11580156103a1573d5f803e3d5ffd5b505050505050505050565b6103c1335f356001600160e01b031916610b13565b6103dd5760405162461bcd60e51b815260040161021990611968565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610426576040516337aab0fd60e11b815260040160405180910390fd5b6001600160a01b038716301461044f5760405163702093cb60e11b815260040160405180910390fd5b5f61045c82840184611b97565b90505f816001811115610471576104716119a5565b0361048957610484838389898989610c3c565b6104c9565b600181600181111561049d5761049d6119a5565b036104b057610484838389898989610e46565b6040516336ad3b5560e21b815260040160405180910390fd5b5050505050505050565b6104e8335f356001600160e01b031916610b13565b6105045760405162461bcd60e51b815260040161021990611968565b336105156040840160208501611757565b6001600160a01b03161461053c576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b61055a611509565b81526020019060019003908161055257905050905061057e36849003840184611bc4565b815f8151811061059057610590611c93565b60200260200101819052505f8033845f806040516020016105b59594939291906119d9565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc9061061590859085903090600401611ca7565b5f604051808303815f87803b15801561062c575f80fd5b505af11580156104c9573d5f803e3d5ffd5b5f546001600160a01b03163314806106cf575060015460405163b700961360e01b81526001600160a01b039091169063b70096139061069090339030906001600160e01b03195f351690600401611d89565b602060405180830381865afa1580156106ab573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106cf9190611db6565b6106d7575f80fd5b600180546001600160a01b0319166001600160a01b03831690811790915560405133907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350565b610737335f356001600160e01b031916610b13565b6107535760405162461bcd60e51b815260040161021990611968565b336107646040860160208701611757565b6001600160a01b03161461078b576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b6107a9611509565b8152602001906001900390816107a15790505090506107cd36869003860186611bc4565b815f815181106107df576107df611c93565b60200260200101819052505f6001338686865f806040516020016108099796959493929190611dd1565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc9061086990859085903090600401611ca7565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b50505050505050505050565b604080515f8152602081019091526060908267ffffffffffffffff8111156108c8576108c8611bb0565b6040519080825280602002602001820160405280156108fb57816020015b60608152602001906001900390816108e65790505b5091505f5b83811015610987576109573086868481811061091e5761091e611c93565b90506020028101906109309190611e48565b8560405160200161094393929190611e8b565b6040516020818303038152906040526112d5565b83828151811061096957610969611c93565b6020026020010181905250808061097f90611eb0565b915050610900565b50505b92915050565b6109a5335f356001600160e01b031916610b13565b6109c15760405162461bcd60e51b815260040161021990611968565b5f6001338686867f000000000000000000000000000000000000000000000000000000000000000087604051602001610a009796959493929190611dd1565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063412638dc90610a62908a908a9086903090600401611a6a565b5f604051808303815f87803b158015610a79575f80fd5b505af1158015610a8b573d5f803e3d5ffd5b5050505050505050505050565b610aad335f356001600160e01b031916610b13565b610ac95760405162461bcd60e51b815260040161021990611968565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b6001545f906001600160a01b03168015801590610b9a575060405163b700961360e01b81526001600160a01b0382169063b700961390610b5b90879030908890600401611d89565b602060405180830381865afa158015610b76573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b9a9190611db6565b80610bb157505f546001600160a01b038581169116145b949350505050565b5f60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610c365760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606401610219565b50505050565b5f808080610c4c898b018b611ec8565b945094509450945050826001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c91573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cb59190611f33565b6001600160a01b0316886001600160a01b031614610cf957604051631469fe1360e21b81526001600160a01b03808a16600483015284166024820152604401610219565b604051633e64ce9960e01b815287905f906001600160a01b03861690633e64ce9990610d2f9085908c9086903090600401611f4e565b6020604051808303815f875af1158015610d4b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d6f919061198e565b905086811115610db1578315610da357610d9e86610d8d8984611e35565b6001600160a01b0385169190610bb9565b610e04565b610d9e8a610d8d8984611e35565b86811015610e04575f610dc48289611e35565b90508315610de657610de16001600160a01b038416883084611347565b610e02565b60405163c2fceaf960e01b815260048101829052602401610219565b505b610e386001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000896113df565b505050505050505050505050565b5f8080808080610e588b8d018d611f79565b96509650965096509650965050846001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ea1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ec59190611f33565b6001600160a01b03168a6001600160a01b031614610f0957604051631469fe1360e21b81526001600160a01b03808c16600483015286166024820152604401610219565b836001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f699190611f33565b6001600160a01b0316896001600160a01b031614610fad57604051631469fe1360e21b81526001600160a01b03808b16600483015285166024820152604401610219565b604051633e64ce9960e01b81525f906001600160a01b03871690633e64ce9990610fe19087908d9086903090600401611f4e565b6020604051808303815f875af1158015610ffd573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611021919061198e565b90505f611179866001600160a01b0316634fb3ccc56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611063573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110879190611f33565b604051634104b9ed60e11b81526001600160a01b038881166004830152919091169063820973da90602401602060405180830381865afa1580156110cd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110f1919061198e565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b7d122b56040518163ffffffff1660e01b8152600401602060405180830381865afa15801561114d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611171919061198e565b8b919061145b565b9050808211156111945761118d8183611e35565b91506111d1565b808210156111cd575f6111a78383611e35565b90508315610de6576111c46001600160a01b0387168a3084611347565b5f9250506111d1565b5f91505b6111e56001600160a01b0386168c836113df565b6040516304eaba2160e51b81526001600160a01b03871690639d5744209061121790889085908e903090600401611f4e565b6020604051808303815f875af1158015611233573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611257919061198e565b5050801561129257821561127e576112796001600160a01b0385168883610bb9565b611292565b6112926001600160a01b0385168c83610bb9565b6112c66001600160a01b038b167f00000000000000000000000000000000000000000000000000000000000000008a6113df565b50505050505050505050505050565b60605f80846001600160a01b0316846040516112f19190612008565b5f60405180830381855af49150503d805f8114611329576040519150601f19603f3d011682016040523d82523d5f602084013e61132e565b606091505b509150915061133e85838361147e565b95945050505050565b5f6040516323b872dd60e01b81526001600160a01b03851660048201526001600160a01b038416602482015282604482015260205f6064835f8a5af13d15601f3d1160015f5114161716915050806113d85760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606401610219565b5050505050565b5f60405163095ea7b360e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610c365760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606401610219565b5f825f19048411830215820261146f575f80fd5b50910281810615159190040190565b6060826114935761148e826114dd565b6114d6565b81511580156114aa57506001600160a01b0384163b155b156114d357604051639996b31560e01b81526001600160a01b0385166004820152602401610219565b50805b9392505050565b8051156114ed5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915290565b6001600160a01b0381168114611506575f80fd5b5f8060408385031215611571575f80fd5b823561157c8161154c565b946020939093013593505050565b5f8083601f84011261159a575f80fd5b50813567ffffffffffffffff8111156115b1575f80fd5b6020830191508360208260081b85010111156115cb575f80fd5b9250929050565b80356115dd8161154c565b919050565b8015158114611506575f80fd5b5f805f8060608587031215611602575f80fd5b843567ffffffffffffffff811115611618575f80fd5b6116248782880161158a565b90955093505060208501356116388161154c565b91506040850135611648816115e2565b939692955090935050565b5f805f805f805f60c0888a031215611669575f80fd5b87356116748161154c565b965060208801356116848161154c565b955060408801356116948161154c565b9450606088013593506080880135925060a088013567ffffffffffffffff808211156116be575f80fd5b818a0191508a601f8301126116d1575f80fd5b8135818111156116df575f80fd5b8b60208285010111156116f0575f80fd5b60208301945080935050505092959891949750929550565b5f6101008284031215611719575f80fd5b50919050565b5f806101208385031215611731575f80fd5b61173b8484611708565b915061010083013561174c8161154c565b809150509250929050565b5f60208284031215611767575f80fd5b81356114d68161154c565b5f805f806101608587031215611786575f80fd5b6117908686611708565b93506101008501356117a18161154c565b92506101208501356117b28161154c565b91506101408501356116488161154c565b5f80602083850312156117d4575f80fd5b823567ffffffffffffffff808211156117eb575f80fd5b818501915085601f8301126117fe575f80fd5b81358181111561180c575f80fd5b8660208260051b8501011115611820575f80fd5b60209290920196919550909350505050565b5f5b8381101561184c578181015183820152602001611834565b50505f910152565b5f815180845261186b816020860160208601611832565b601f01601f19169290920160200192915050565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156118d257603f198886030184526118c0858351611854565b945092850192908501906001016118a4565b5092979650505050505050565b5f805f805f8060a087890312156118f4575f80fd5b863567ffffffffffffffff81111561190a575f80fd5b61191689828a0161158a565b909750955050602087013561192a8161154c565b9350604087013561193a8161154c565b9250606087013561194a8161154c565b9150608087013561195a816115e2565b809150509295509295509295565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b5f6020828403121561199e575f80fd5b5051919050565b634e487b7160e01b5f52602160045260245ffd5b600281106119d557634e487b7160e01b5f52602160045260245ffd5b9052565b60a081016119e782886119b9565b6001600160a01b03958616602083015293909416604085015290151560608401521515608090920191909152919050565b80356001600160601b03811681146115dd575f80fd5b80356001600160801b03811681146115dd575f80fd5b803564ffffffffff811681146115dd575f80fd5b803562ffffff811681146115dd575f80fd5b60608082528181018590525f90608080840188845b89811015611b5b576001600160601b03611a9883611a18565b168352602080830135611aaa8161154c565b6001600160a01b031690840152604082810135611ac68161154c565b6001600160a01b031690840152611ade828601611a2e565b6001600160801b031685840152611af6828501611a2e565b6001600160801b03168484015260a0611b10838201611a44565b64ffffffffff169084015260c0611b28838201611a58565b62ffffff169084015260e0611b3e838201611a58565b62ffffff1690840152610100928301929190910190600101611a7f565b50508481036020860152611b6f8188611854565b935050505061133e60408301846001600160a01b03169052565b8035600281106115dd575f80fd5b5f60208284031215611ba7575f80fd5b6114d682611b89565b634e487b7160e01b5f52604160045260245ffd5b5f610100808385031215611bd6575f80fd5b6040519081019067ffffffffffffffff82118183101715611c0557634e487b7160e01b5f52604160045260245ffd5b81604052611c1284611a18565b8152611c20602085016115d2565b6020820152611c31604085016115d2565b6040820152611c4260608501611a2e565b6060820152611c5360808501611a2e565b6080820152611c6460a08501611a44565b60a0820152611c7560c08501611a58565b60c0820152611c8660e08501611a58565b60e0820152949350505050565b634e487b7160e01b5f52603260045260245ffd5b606080825284518282018190525f9190608090818501906020808a01865b83811015611d5b57815180516001600160601b03168652838101516001600160a01b039081168588015260408083015190911690870152878101516001600160801b039081168988015287820151168787015260a08082015164ffffffffff169087015260c08082015162ffffff9081169188019190915260e09182015116908601526101009094019390820190600101611cc5565b50508683039087015250611d6f8188611854565b9350505050610bb160408301846001600160a01b03169052565b6001600160a01b0393841681529190921660208201526001600160e01b0319909116604082015260600190565b5f60208284031215611dc6575f80fd5b81516114d6816115e2565b60e08101611ddf828a6119b9565b6001600160a01b0397881660208301529587166040820152938616606085015291909416608083015292151560a082015291151560c090920191909152919050565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561098a5761098a611e21565b5f808335601e19843603018112611e5d575f80fd5b83018035915067ffffffffffffffff821115611e77575f80fd5b6020019150368190038213156115cb575f80fd5b828482375f8382015f81528351611ea6818360208801611832565b0195945050505050565b5f60018201611ec157611ec1611e21565b5060010190565b5f805f805f60a08688031215611edc575f80fd5b611ee586611b89565b94506020860135611ef58161154c565b93506040860135611f058161154c565b92506060860135611f15816115e2565b91506080860135611f25816115e2565b809150509295509295909350565b5f60208284031215611f43575f80fd5b81516114d68161154c565b6001600160a01b03948516815260208101939093526040830191909152909116606082015260800190565b5f805f805f805f60e0888a031215611f8f575f80fd5b611f9888611b89565b96506020880135611fa88161154c565b95506040880135611fb88161154c565b94506060880135611fc88161154c565b93506080880135611fd88161154c565b925060a0880135611fe8816115e2565b915060c0880135611ff8816115e2565b8091505092959891949750929550565b5f8251612019818460208701611832565b919091019291505056fea26469706673582212206e8ea341023cedbde5f12cd60c95b72da9cb49a98f11c49d49585ec08b5cbaad64736f6c634300081500330000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405cf833e402365821788eed9633df1601cc5a970000000000000000000000000000000000000000000000000000000000000001
Deployed Bytecode
0x608060405234801561000f575f80fd5b50600436106100b1575f3560e01c80638da5cb5b1161006e5780638da5cb5b146101525780638f3866081461017c578063ac9650d81461018f578063bc9961f7146101af578063bf7e214f146101c2578063f2fde38b146101d5575f80fd5b806357376198146100b55780635ff8a71f146100ca57806367aa0416146100dd5780636b9f9fef146100f057806372faf4a41461012c5780637a9e5e4b1461013f575b5f80fd5b6100c86100c3366004611560565b6101e8565b005b6100c86100d83660046115ef565b6102ab565b6100c86100eb366004611653565b6103ac565b6101177f000000000000000000000000000000000000000000000000000000000000000181565b60405190151581526020015b60405180910390f35b6100c861013a36600461171f565b6104d3565b6100c861014d366004611757565b61063e565b5f54610164906001600160a01b031681565b6040516001600160a01b039091168152602001610123565b6100c861018a366004611772565b610722565b6101a261019d3660046117c3565b61089e565b604051610123919061187f565b6100c86101bd3660046118df565b610990565b600154610164906001600160a01b031681565b6100c86101e3366004611757565b610a98565b6101fd335f356001600160e01b031916610b13565b6102225760405162461bcd60e51b815260040161021990611968565b60405180910390fd5b5f198103610293576040516370a0823160e01b81523060048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561026c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610290919061198e565b90505b6102a76001600160a01b0383163383610bb9565b5050565b6102c0335f356001600160e01b031916610b13565b6102dc5760405162461bcd60e51b815260040161021990611968565b5f8033847f0000000000000000000000000000000000000000000000000000000000000001856040516020016103169594939291906119d9565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f000000000000000000000000405cf833e402365821788eed9633df1601cc5a97169063412638dc90610378908890889086903090600401611a6a565b5f604051808303815f87803b15801561038f575f80fd5b505af11580156103a1573d5f803e3d5ffd5b505050505050505050565b6103c1335f356001600160e01b031916610b13565b6103dd5760405162461bcd60e51b815260040161021990611968565b336001600160a01b037f000000000000000000000000405cf833e402365821788eed9633df1601cc5a971614610426576040516337aab0fd60e11b815260040160405180910390fd5b6001600160a01b038716301461044f5760405163702093cb60e11b815260040160405180910390fd5b5f61045c82840184611b97565b90505f816001811115610471576104716119a5565b0361048957610484838389898989610c3c565b6104c9565b600181600181111561049d5761049d6119a5565b036104b057610484838389898989610e46565b6040516336ad3b5560e21b815260040160405180910390fd5b5050505050505050565b6104e8335f356001600160e01b031916610b13565b6105045760405162461bcd60e51b815260040161021990611968565b336105156040840160208501611757565b6001600160a01b03161461053c576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b61055a611509565b81526020019060019003908161055257905050905061057e36849003840184611bc4565b815f8151811061059057610590611c93565b60200260200101819052505f8033845f806040516020016105b59594939291906119d9565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f000000000000000000000000405cf833e402365821788eed9633df1601cc5a97169063412638dc9061061590859085903090600401611ca7565b5f604051808303815f87803b15801561062c575f80fd5b505af11580156104c9573d5f803e3d5ffd5b5f546001600160a01b03163314806106cf575060015460405163b700961360e01b81526001600160a01b039091169063b70096139061069090339030906001600160e01b03195f351690600401611d89565b602060405180830381865afa1580156106ab573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106cf9190611db6565b6106d7575f80fd5b600180546001600160a01b0319166001600160a01b03831690811790915560405133907fa3396fd7f6e0a21b50e5089d2da70d5ac0a3bbbd1f617a93f134b76389980198905f90a350565b610737335f356001600160e01b031916610b13565b6107535760405162461bcd60e51b815260040161021990611968565b336107646040860160208701611757565b6001600160a01b03161461078b576040516303279bc360e41b815260040160405180910390fd5b6040805160018082528183019092525f91816020015b6107a9611509565b8152602001906001900390816107a15790505090506107cd36869003860186611bc4565b815f815181106107df576107df611c93565b60200260200101819052505f6001338686865f806040516020016108099796959493929190611dd1565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f000000000000000000000000405cf833e402365821788eed9633df1601cc5a97169063412638dc9061086990859085903090600401611ca7565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b50505050505050505050565b604080515f8152602081019091526060908267ffffffffffffffff8111156108c8576108c8611bb0565b6040519080825280602002602001820160405280156108fb57816020015b60608152602001906001900390816108e65790505b5091505f5b83811015610987576109573086868481811061091e5761091e611c93565b90506020028101906109309190611e48565b8560405160200161094393929190611e8b565b6040516020818303038152906040526112d5565b83828151811061096957610969611c93565b6020026020010181905250808061097f90611eb0565b915050610900565b50505b92915050565b6109a5335f356001600160e01b031916610b13565b6109c15760405162461bcd60e51b815260040161021990611968565b5f6001338686867f000000000000000000000000000000000000000000000000000000000000000187604051602001610a009796959493929190611dd1565b60408051601f19818403018152908290526310498e3760e21b825291506001600160a01b037f000000000000000000000000405cf833e402365821788eed9633df1601cc5a97169063412638dc90610a62908a908a9086903090600401611a6a565b5f604051808303815f87803b158015610a79575f80fd5b505af1158015610a8b573d5f803e3d5ffd5b5050505050505050505050565b610aad335f356001600160e01b031916610b13565b610ac95760405162461bcd60e51b815260040161021990611968565b5f80546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b6001545f906001600160a01b03168015801590610b9a575060405163b700961360e01b81526001600160a01b0382169063b700961390610b5b90879030908890600401611d89565b602060405180830381865afa158015610b76573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b9a9190611db6565b80610bb157505f546001600160a01b038581169116145b949350505050565b5f60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610c365760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606401610219565b50505050565b5f808080610c4c898b018b611ec8565b945094509450945050826001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c91573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cb59190611f33565b6001600160a01b0316886001600160a01b031614610cf957604051631469fe1360e21b81526001600160a01b03808a16600483015284166024820152604401610219565b604051633e64ce9960e01b815287905f906001600160a01b03861690633e64ce9990610d2f9085908c9086903090600401611f4e565b6020604051808303815f875af1158015610d4b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d6f919061198e565b905086811115610db1578315610da357610d9e86610d8d8984611e35565b6001600160a01b0385169190610bb9565b610e04565b610d9e8a610d8d8984611e35565b86811015610e04575f610dc48289611e35565b90508315610de657610de16001600160a01b038416883084611347565b610e02565b60405163c2fceaf960e01b815260048101829052602401610219565b505b610e386001600160a01b0383167f000000000000000000000000405cf833e402365821788eed9633df1601cc5a97896113df565b505050505050505050505050565b5f8080808080610e588b8d018d611f79565b96509650965096509650965050846001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ea1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ec59190611f33565b6001600160a01b03168a6001600160a01b031614610f0957604051631469fe1360e21b81526001600160a01b03808c16600483015286166024820152604401610219565b836001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f699190611f33565b6001600160a01b0316896001600160a01b031614610fad57604051631469fe1360e21b81526001600160a01b03808b16600483015285166024820152604401610219565b604051633e64ce9960e01b81525f906001600160a01b03871690633e64ce9990610fe19087908d9086903090600401611f4e565b6020604051808303815f875af1158015610ffd573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611021919061198e565b90505f611179866001600160a01b0316634fb3ccc56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611063573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110879190611f33565b604051634104b9ed60e11b81526001600160a01b038881166004830152919091169063820973da90602401602060405180830381865afa1580156110cd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110f1919061198e565b7f000000000000000000000000405cf833e402365821788eed9633df1601cc5a976001600160a01b031663b7d122b56040518163ffffffff1660e01b8152600401602060405180830381865afa15801561114d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611171919061198e565b8b919061145b565b9050808211156111945761118d8183611e35565b91506111d1565b808210156111cd575f6111a78383611e35565b90508315610de6576111c46001600160a01b0387168a3084611347565b5f9250506111d1565b5f91505b6111e56001600160a01b0386168c836113df565b6040516304eaba2160e51b81526001600160a01b03871690639d5744209061121790889085908e903090600401611f4e565b6020604051808303815f875af1158015611233573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611257919061198e565b5050801561129257821561127e576112796001600160a01b0385168883610bb9565b611292565b6112926001600160a01b0385168c83610bb9565b6112c66001600160a01b038b167f000000000000000000000000405cf833e402365821788eed9633df1601cc5a978a6113df565b50505050505050505050505050565b60605f80846001600160a01b0316846040516112f19190612008565b5f60405180830381855af49150503d805f8114611329576040519150601f19603f3d011682016040523d82523d5f602084013e61132e565b606091505b509150915061133e85838361147e565b95945050505050565b5f6040516323b872dd60e01b81526001600160a01b03851660048201526001600160a01b038416602482015282604482015260205f6064835f8a5af13d15601f3d1160015f5114161716915050806113d85760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606401610219565b5050505050565b5f60405163095ea7b360e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610c365760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606401610219565b5f825f19048411830215820261146f575f80fd5b50910281810615159190040190565b6060826114935761148e826114dd565b6114d6565b81511580156114aa57506001600160a01b0384163b155b156114d357604051639996b31560e01b81526001600160a01b0385166004820152602401610219565b50805b9392505050565b8051156114ed5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915290565b6001600160a01b0381168114611506575f80fd5b5f8060408385031215611571575f80fd5b823561157c8161154c565b946020939093013593505050565b5f8083601f84011261159a575f80fd5b50813567ffffffffffffffff8111156115b1575f80fd5b6020830191508360208260081b85010111156115cb575f80fd5b9250929050565b80356115dd8161154c565b919050565b8015158114611506575f80fd5b5f805f8060608587031215611602575f80fd5b843567ffffffffffffffff811115611618575f80fd5b6116248782880161158a565b90955093505060208501356116388161154c565b91506040850135611648816115e2565b939692955090935050565b5f805f805f805f60c0888a031215611669575f80fd5b87356116748161154c565b965060208801356116848161154c565b955060408801356116948161154c565b9450606088013593506080880135925060a088013567ffffffffffffffff808211156116be575f80fd5b818a0191508a601f8301126116d1575f80fd5b8135818111156116df575f80fd5b8b60208285010111156116f0575f80fd5b60208301945080935050505092959891949750929550565b5f6101008284031215611719575f80fd5b50919050565b5f806101208385031215611731575f80fd5b61173b8484611708565b915061010083013561174c8161154c565b809150509250929050565b5f60208284031215611767575f80fd5b81356114d68161154c565b5f805f806101608587031215611786575f80fd5b6117908686611708565b93506101008501356117a18161154c565b92506101208501356117b28161154c565b91506101408501356116488161154c565b5f80602083850312156117d4575f80fd5b823567ffffffffffffffff808211156117eb575f80fd5b818501915085601f8301126117fe575f80fd5b81358181111561180c575f80fd5b8660208260051b8501011115611820575f80fd5b60209290920196919550909350505050565b5f5b8381101561184c578181015183820152602001611834565b50505f910152565b5f815180845261186b816020860160208601611832565b601f01601f19169290920160200192915050565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156118d257603f198886030184526118c0858351611854565b945092850192908501906001016118a4565b5092979650505050505050565b5f805f805f8060a087890312156118f4575f80fd5b863567ffffffffffffffff81111561190a575f80fd5b61191689828a0161158a565b909750955050602087013561192a8161154c565b9350604087013561193a8161154c565b9250606087013561194a8161154c565b9150608087013561195a816115e2565b809150509295509295509295565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b5f6020828403121561199e575f80fd5b5051919050565b634e487b7160e01b5f52602160045260245ffd5b600281106119d557634e487b7160e01b5f52602160045260245ffd5b9052565b60a081016119e782886119b9565b6001600160a01b03958616602083015293909416604085015290151560608401521515608090920191909152919050565b80356001600160601b03811681146115dd575f80fd5b80356001600160801b03811681146115dd575f80fd5b803564ffffffffff811681146115dd575f80fd5b803562ffffff811681146115dd575f80fd5b60608082528181018590525f90608080840188845b89811015611b5b576001600160601b03611a9883611a18565b168352602080830135611aaa8161154c565b6001600160a01b031690840152604082810135611ac68161154c565b6001600160a01b031690840152611ade828601611a2e565b6001600160801b031685840152611af6828501611a2e565b6001600160801b03168484015260a0611b10838201611a44565b64ffffffffff169084015260c0611b28838201611a58565b62ffffff169084015260e0611b3e838201611a58565b62ffffff1690840152610100928301929190910190600101611a7f565b50508481036020860152611b6f8188611854565b935050505061133e60408301846001600160a01b03169052565b8035600281106115dd575f80fd5b5f60208284031215611ba7575f80fd5b6114d682611b89565b634e487b7160e01b5f52604160045260245ffd5b5f610100808385031215611bd6575f80fd5b6040519081019067ffffffffffffffff82118183101715611c0557634e487b7160e01b5f52604160045260245ffd5b81604052611c1284611a18565b8152611c20602085016115d2565b6020820152611c31604085016115d2565b6040820152611c4260608501611a2e565b6060820152611c5360808501611a2e565b6080820152611c6460a08501611a44565b60a0820152611c7560c08501611a58565b60c0820152611c8660e08501611a58565b60e0820152949350505050565b634e487b7160e01b5f52603260045260245ffd5b606080825284518282018190525f9190608090818501906020808a01865b83811015611d5b57815180516001600160601b03168652838101516001600160a01b039081168588015260408083015190911690870152878101516001600160801b039081168988015287820151168787015260a08082015164ffffffffff169087015260c08082015162ffffff9081169188019190915260e09182015116908601526101009094019390820190600101611cc5565b50508683039087015250611d6f8188611854565b9350505050610bb160408301846001600160a01b03169052565b6001600160a01b0393841681529190921660208201526001600160e01b0319909116604082015260600190565b5f60208284031215611dc6575f80fd5b81516114d6816115e2565b60e08101611ddf828a6119b9565b6001600160a01b0397881660208301529587166040820152938616606085015291909416608083015292151560a082015291151560c090920191909152919050565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561098a5761098a611e21565b5f808335601e19843603018112611e5d575f80fd5b83018035915067ffffffffffffffff821115611e77575f80fd5b6020019150368190038213156115cb575f80fd5b828482375f8382015f81528351611ea6818360208801611832565b0195945050505050565b5f60018201611ec157611ec1611e21565b5060010190565b5f805f805f60a08688031215611edc575f80fd5b611ee586611b89565b94506020860135611ef58161154c565b93506040860135611f058161154c565b92506060860135611f15816115e2565b91506080860135611f25816115e2565b809150509295509295909350565b5f60208284031215611f43575f80fd5b81516114d68161154c565b6001600160a01b03948516815260208101939093526040830191909152909116606082015260800190565b5f805f805f805f60e0888a031215611f8f575f80fd5b611f9888611b89565b96506020880135611fa88161154c565b95506040880135611fb88161154c565b94506060880135611fc88161154c565b93506080880135611fd88161154c565b925060a0880135611fe8816115e2565b915060c0880135611ff8816115e2565b8091505092959891949750929550565b5f8251612019818460208701611832565b919091019291505056fea26469706673582212206e8ea341023cedbde5f12cd60c95b72da9cb49a98f11c49d49585ec08b5cbaad64736f6c63430008150033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405cf833e402365821788eed9633df1601cc5a970000000000000000000000000000000000000000000000000000000000000001
-----Decoded View---------------
Arg [0] : _owner (address): 0x5F2F11ad8656439d5C14d9B351f8b09cDaC2A02d
Arg [1] : _auth (address): 0x0000000000000000000000000000000000000000
Arg [2] : _queue (address): 0x405Cf833e402365821788EEd9633dF1601Cc5a97
Arg [3] : _excessToSolverNonSelfSolve (bool): True
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 0000000000000000000000005f2f11ad8656439d5c14d9b351f8b09cdac2a02d
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [2] : 000000000000000000000000405cf833e402365821788eed9633df1601cc5a97
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000001
Deployed Bytecode Sourcemap
542:11892:19:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2463:210;;;;;;:::i;:::-;;:::i;:::-;;2862:395;;;;;;:::i;:::-;;:::i;5912:998::-;;;;;;:::i;:::-;;:::i;1617:48::-;;;;;;;;3279:14:25;;3272:22;3254:41;;3242:2;3227:18;1617:48:19;;;;;;;;4279:542;;;;;;:::i;:::-;;:::i;1523:434:10:-;;;;;;:::i;:::-;;:::i;562:20::-;;;;;-1:-1:-1;;;;;562:20:10;;;;;;-1:-1:-1;;;;;4428:32:25;;;4410:51;;4398:2;4383:18;562:20:10;4264:203:25;5071:663:19;;;;;;:::i;:::-;;:::i;1208:484:6:-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;3495:584:19:-;;;;;;:::i;:::-;;:::i;589:26:10:-;;;;;-1:-1:-1;;;;;589:26:10;;;1963:164;;;;;;:::i;:::-;;:::i;2463:210:19:-;902:33:10;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;2550:6:19::1;:27:::0;2546:72:::1;;2588:30;::::0;-1:-1:-1;;;2588:30:19;;2612:4:::1;2588:30;::::0;::::1;4410:51:25::0;-1:-1:-1;;;;;2588:15:19;::::1;::::0;::::1;::::0;4383:18:25;;2588:30:19::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2579:39;;2546:72;2628:38;-1:-1:-1::0;;;;;2628:18:19;::::1;2647:10;2659:6:::0;2628:18:::1;:38::i;:::-;2463:210:::0;;:::o;2862:395::-;902:33:10;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;3042:22:19::1;3090:23:::0;3115:10:::1;3127:6;3135:26;3163:12;3079:97;;;;;;;;;;;;:::i;:::-;;::::0;;-1:-1:-1;;3079:97:19;;::::1;::::0;;;;;;;-1:-1:-1;;;3187:63:19;;3079:97;-1:-1:-1;;;;;;3187:5:19::1;:27;::::0;::::1;::::0;:63:::1;::::0;3215:8;;;;3079:97;;3244:4:::1;::::0;3187:63:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;3032:225;2862:395:::0;;;;:::o;5912:998::-;902:33:10;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;6154:10:19::1;-1:-1:-1::0;;;;;6176:5:19::1;6154:28;;6150:67;;6191:26;;-1:-1:-1::0;;;6191:26:19::1;;;;;;;;;;;6150:67;-1:-1:-1::0;;;;;6231:26:19;::::1;6252:4;6231:26;6227:70;;6266:31;;-1:-1:-1::0;;;6266:31:19::1;;;;;;;;;;;6227:70;6308:19;6330:34;::::0;;::::1;6341:9:::0;6330:34:::1;:::i;:::-;6308:56:::0;-1:-1:-1;6392:23:19::1;6379:9;:36;;;;;;;;:::i;:::-;::::0;6375:529:::1;;6431:83;6450:9;;6461:11;6474:10;6486:11;6499:14;6431:18;:83::i;:::-;6375:529;;;6548:28;6535:9;:41;;;;;;;;:::i;:::-;::::0;6531:373:::1;;6592:87;6615:9;;6626:11;6639:10;6651:11;6664:14;6592:22;:87::i;6531:373::-;6863:30;;-1:-1:-1::0;;;6863:30:19::1;;;;;;;;;;;6531:373;6140:770;5912:998:::0;;;;;;;:::o;4279:542::-;902:33:10;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;4451:10:19::1;4435:12;::::0;;;::::1;::::0;::::1;;:::i;:::-;-1:-1:-1::0;;;;;4435:26:19::1;;4431:64;;4470:25;;-1:-1:-1::0;;;4470:25:19::1;;;;;;;;;;;4431:64;4561:43;::::0;;4602:1:::1;4561:43:::0;;;;;::::1;::::0;;;4506:52:::1;::::0;4561:43:::1;;;;;;:::i;:::-;;;;;;;;;;;;;;;-1:-1:-1::0;4506:98:19;-1:-1:-1;4614:21:19::1;;::::0;;::::1;::::0;::::1;4628:7:::0;4614:21:::1;:::i;:::-;:8;4623:1;4614:11;;;;;;;;:::i;:::-;;;;;;:21;;;;4646:22;4682:23:::0;4707:10:::1;4719:6;4727:5;4734::::0;4671:69:::1;;;;;;;;;;;;:::i;:::-;;::::0;;-1:-1:-1;;4671:69:19;;::::1;::::0;;;;;;;-1:-1:-1;;;4751:63:19;;4671:69;-1:-1:-1;;;;;;4751:5:19::1;:27;::::0;::::1;::::0;:63:::1;::::0;4779:8;;4671:69;;4808:4:::1;::::0;4751:63:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;1523:434:10::0;1794:5;;-1:-1:-1;;;;;1794:5:10;1780:10;:19;;:76;;-1:-1:-1;1803:9:10;;:53;;-1:-1:-1;;;1803:53:10;;-1:-1:-1;;;;;1803:9:10;;;;:17;;:53;;1821:10;;1841:4;;-1:-1:-1;;;;;;1803:9:10;1848:7;;;1803:53;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1772:85;;;;;;1868:9;:24;;-1:-1:-1;;;;;;1868:24:10;-1:-1:-1;;;;;1868:24:10;;;;;;;;1908:42;;1925:10;;1908:42;;-1:-1:-1;;1908:42:10;1523:434;:::o;5071:663:19:-;902:33:10;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;5314:10:19::1;5298:12;::::0;;;::::1;::::0;::::1;;:::i;:::-;-1:-1:-1::0;;;;;5298:26:19::1;;5294:64;;5333:25;;-1:-1:-1::0;;;5333:25:19::1;;;;;;;;;;;5294:64;5424:43;::::0;;5465:1:::1;5424:43:::0;;;;;::::1;::::0;;;5369:52:::1;::::0;5424:43:::1;;;;;;:::i;:::-;;;;;;;;;;;;;;;-1:-1:-1::0;5369:98:19;-1:-1:-1;5477:21:19::1;;::::0;;::::1;::::0;::::1;5491:7:::0;5477:21:::1;:::i;:::-;:8;5486:1;5477:11;;;;;;;;:::i;:::-;;;;;;:21;;;;5509:22;5557:28;5587:10;5599;5611:8;5621:17;5640:5;5647::::0;5546:107:::1;;;;;;;;;;;;;;:::i;:::-;;::::0;;-1:-1:-1;;5546:107:19;;::::1;::::0;;;;;;;-1:-1:-1;;;5664:63:19;;5546:107;-1:-1:-1;;;;;;5664:5:19::1;:27;::::0;::::1;::::0;:63:::1;::::0;5692:8;;5546:107;;5721:4:::1;::::0;5664:63:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;5284:450;;5071:663:::0;;;;:::o;1208:484:6:-;1374:12;;;1310:20;1374:12;;;;;;;;1276:22;;1485:4;1473:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1463:34;;1512:9;1507:155;1527:15;;;1507:155;;;1576:75;1613:4;1633;;1638:1;1633:7;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;1642;1620:30;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;1576:28;:75::i;:::-;1563:7;1571:1;1563:10;;;;;;;;:::i;:::-;;;;;;:88;;;;1544:3;;;;;:::i;:::-;;;;1507:155;;;;1671:14;1208:484;;;;;:::o;3495:584:19:-;902:33:10;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;3744:22:19::1;3793:28;3835:10;3859;3883:8;3905:17;3936:26;3976:12;3769:229;;;;;;;;;;;;;;:::i;:::-;;::::0;;-1:-1:-1;;3769:229:19;;::::1;::::0;;;;;;;-1:-1:-1;;;4009:63:19;;3769:229;-1:-1:-1;;;;;;4009:5:19::1;:27;::::0;::::1;::::0;:63:::1;::::0;4037:8;;;;3769:229;;4066:4:::1;::::0;4009:63:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;3734:345;3495:584:::0;;;;;;:::o;1963:164:10:-;902:33;915:10;927:7;;-1:-1:-1;;;;;;927:7:10;902:12;:33::i;:::-;894:58;;;;-1:-1:-1;;;894:58:10;;;;;;;:::i;:::-;2046:5:::1;:16:::0;;-1:-1:-1;;;;;;2046:16:10::1;-1:-1:-1::0;;;;;2046:16:10;::::1;::::0;;::::1;::::0;;2078:42:::1;::::0;2046:16;;2099:10:::1;::::0;2078:42:::1;::::0;2046:5;2078:42:::1;1963:164:::0;:::o;977:540::-;1097:9;;1064:4;;-1:-1:-1;;;;;1097:9:10;1415:27;;;;;:77;;-1:-1:-1;1446:46:10;;-1:-1:-1;;;1446:46:10;;-1:-1:-1;;;;;1446:12:10;;;;;:46;;1459:4;;1473;;1480:11;;1446:46;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1414:96;;;-1:-1:-1;1505:5:10;;-1:-1:-1;;;;;1497:13:10;;;1505:5;;1497:13;1414:96;1407:103;977:540;-1:-1:-1;;;;977:540:10:o;3116:1607:15:-;3228:12;3398:4;3392:11;-1:-1:-1;;;3521:17:15;3514:93;-1:-1:-1;;;;;3658:2:15;3654:51;3650:1;3631:17;3627:25;3620:86;3792:6;3787:2;3768:17;3764:26;3757:42;4644:2;4641:1;4637:2;4618:17;4615:1;4608:5;4601;4596:51;4165:16;4158:24;4152:2;4134:16;4131:24;4127:1;4123;4117:8;4114:15;4110:46;4107:76;3907:754;3896:765;;;4689:7;4681:35;;;;-1:-1:-1;;;4681:35:15;;20248:2:25;4681:35:15;;;20230:21:25;20287:2;20267:18;;;20260:30;-1:-1:-1;;;20306:18:25;;;20299:45;20361:18;;4681:35:15;20046:339:25;4681:35:15;3218:1505;3116:1607;;;:::o;7130:1824:19:-;7338:20;;;;7450:84;;;;7461:9;7450:84;:::i;:::-;7335:199;;;;;;;;;7572:6;-1:-1:-1;;;;;7572:12:19;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;7549:38:19;:11;-1:-1:-1;;;;;7549:38:19;;7545:146;;7610:70;;-1:-1:-1;;;7610:70:19;;-1:-1:-1;;;;;21721:15:25;;;7610:70:19;;;21703:34:25;21773:15;;21753:18;;;21746:43;21638:18;;7610:70:19;21491:304:25;7545:146:19;7821:57;;-1:-1:-1;;;7821:57:19;;7721:10;;7701:11;;-1:-1:-1;;;;;7821:19:19;;;;;:57;;7721:10;;7848:11;;7701;;7872:4;;7821:57;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;7801:77;;7905:14;7893:9;:26;7889:892;;;8164:14;8160:211;;;8198:60;8217:12;8231:26;8243:14;8231:9;:26;:::i;:::-;-1:-1:-1;;;;;8198:18:19;;;:60;:18;:60::i;:::-;7889:892;;8160:211;8297:59;8316:11;8329:26;8341:14;8329:9;:26;:::i;7889:892::-;8403:14;8391:9;:26;8387:394;;;8514:15;8532:26;8549:9;8532:14;:26;:::i;:::-;8514:44;;8576:12;8572:199;;;8608:60;-1:-1:-1;;;;;8608:22:19;;8631:12;8653:4;8660:7;8608:22;:60::i;:::-;8572:199;;;8714:42;;-1:-1:-1;;;8714:42:19;;;;;22420:25:25;;;22393:18;;8714:42:19;22274:177:25;8572:199:19;8419:362;8387:394;8898:49;-1:-1:-1;;;;;8898:17:19;;8924:5;8932:14;8898:17;:49::i;:::-;7325:1629;;;;;;7130:1824;;;;;;:::o;9112:3320::-;9356:20;;;;;;9592:142;;;;9616:9;9592:142;:::i;:::-;9328:406;;;;;;;;;;;;;9776:10;-1:-1:-1;;;;;9776:16:19;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;9749:46:19;:15;-1:-1:-1;;;;;9749:46:19;;9745:162;;9818:78;;-1:-1:-1;;;9818:78:19;;-1:-1:-1;;;;;21721:15:25;;;9818:78:19;;;21703:34:25;21773:15;;21753:18;;;21746:43;21638:18;;9818:78:19;21491:304:25;9745:162:19;9946:8;-1:-1:-1;;;;;9946:14:19;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;9921:42:19;:13;-1:-1:-1;;;;;9921:42:19;;9917:154;;9986:74;;-1:-1:-1;;;9986:74:19;;-1:-1:-1;;;;;21721:15:25;;;9986:74:19;;;21703:34:25;21773:15;;21753:18;;;21746:43;21638:18;;9986:74:19;21491:304:25;9917:154:19;10173:73;;-1:-1:-1;;;10173:73:19;;10150:20;;-1:-1:-1;;;;;10173:23:19;;;;;:73;;10197:17;;10216:11;;10150:20;;10240:4;;10173:73;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10150:96;;10481:34;10518:153;10559:8;-1:-1:-1;;;;;10559:19:19;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:59;;-1:-1:-1;;;10559:59:19;;-1:-1:-1;;;;;4428:32:25;;;10559:59:19;;;4410:51:25;10559:40:19;;;;;;;4383:18:25;;10559:59:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10639:5;-1:-1:-1;;;;;10620:35:19;;:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10518:14;;:153;:23;:153::i;:::-;10481:190;;10704:26;10689:12;:41;10685:771;;;10837:41;10852:26;10837:12;:41;:::i;:::-;10822:56;;10685:771;;;10918:26;10903:12;:41;10899:557;;;11049:15;11067:41;11096:12;11067:26;:41;:::i;:::-;11049:59;;11130:12;11126:227;;;11166:72;-1:-1:-1;;;;;11166:34:19;;11201:12;11223:4;11230:7;11166:34;:72::i;:::-;11385:1;11370:16;;10946:455;10899:557;;;11440:1;11425:16;;10899:557;11540:72;-1:-1:-1;;;;;11540:29:19;;11570:13;11585:26;11540:29;:72::i;:::-;11695:98;;-1:-1:-1;;;11695:98:19;;-1:-1:-1;;;;;11695:20:19;;;;;:98;;11716:17;;11735:26;;11763:14;;11787:4;;11695:98;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;12036:16:19;;12032:257;;12072:14;12068:211;;;12106:58;-1:-1:-1;;;;;12106:30:19;;12137:12;12151;12106:30;:58::i;:::-;12068:211;;;12203:61;-1:-1:-1;;;;;12203:30:19;;12234:15;12251:12;12203:30;:61::i;:::-;12361:64;-1:-1:-1;;;;;12361:32:19;;12402:5;12410:14;12361:32;:64::i;:::-;9318:3114;;;;;;;9112:3320;;;;;;:::o;4106:253:4:-;4189:12;4214;4228:23;4255:6;-1:-1:-1;;;;;4255:19:4;4275:4;4255:25;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4213:67;;;;4297:55;4324:6;4332:7;4341:10;4297:26;:55::i;:::-;4290:62;4106:253;-1:-1:-1;;;;;4106:253:4:o;1328:1782:15:-;1466:12;1636:4;1630:11;-1:-1:-1;;;1759:17:15;1752:93;-1:-1:-1;;;;;1896:4:15;1892:53;1888:1;1869:17;1865:25;1858:88;-1:-1:-1;;;;;2038:2:15;2034:51;2029:2;2010:17;2006:26;1999:87;2172:6;2167:2;2148:17;2144:26;2137:42;3026:2;3023:1;3018:3;2999:17;2996:1;2989:5;2982;2977:52;2545:16;2538:24;2532:2;2514:16;2511:24;2507:1;2503;2497:8;2494:15;2490:46;2487:76;2287:756;2276:767;;;3071:7;3063:40;;;;-1:-1:-1;;;3063:40:15;;25103:2:25;3063:40:15;;;25085:21:25;25142:2;25122:18;;;25115:30;-1:-1:-1;;;25161:18:25;;;25154:50;25221:18;;3063:40:15;24901:344:25;3063:40:15;1456:1654;1328:1782;;;;:::o;4729:1605::-;4840:12;5010:4;5004:11;-1:-1:-1;;;5133:17:15;5126:93;-1:-1:-1;;;;;5270:2:15;5266:51;5262:1;5243:17;5239:25;5232:86;5404:6;5399:2;5380:17;5376:26;5369:42;6256:2;6253:1;6249:2;6230:17;6227:1;6220:5;6213;6208:51;5777:16;5770:24;5764:2;5746:16;5743:24;5739:1;5735;5729:8;5726:15;5722:46;5719:76;5519:754;5508:765;;;6301:7;6293:34;;;;-1:-1:-1;;;6293:34:15;;25452:2:25;6293:34:15;;;25434:21:25;25491:2;25471:18;;;25464:30;-1:-1:-1;;;25510:18:25;;;25503:44;25564:18;;6293:34:15;25250:338:25;2096:672:13;2210:9;2458:1;-1:-1:-1;;2441:19:13;2438:1;2435:26;2432:1;2428:34;2421:42;2408:11;2404:60;2394:116;;2494:1;2491;2484:12;2394:116;-1:-1:-1;2728:9:13;;2691:27;;;2688:34;;2724:27;;;2684:68;;2096:672::o;4625:582:4:-;4769:12;4798:7;4793:408;;4821:19;4829:10;4821:7;:19::i;:::-;4793:408;;;5045:17;;:22;:49;;;;-1:-1:-1;;;;;;5071:18:4;;;:23;5045:49;5041:119;;;5121:24;;-1:-1:-1;;;5121:24:4;;-1:-1:-1;;;;;4428:32:25;;5121:24:4;;;4410:51:25;4383:18;;5121:24:4;4264:203:25;5041:119:4;-1:-1:-1;5180:10:4;4793:408;4625:582;;;;;:::o;5743:516::-;5874:17;;:21;5870:383;;6102:10;6096:17;6158:15;6145:10;6141:2;6137:19;6130:44;5870:383;6225:17;;-1:-1:-1;;;6225:17:4;;;;;;;;;;;5870:383;5743:516;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:138:25:-;-1:-1:-1;;;;;96:31:25;;86:42;;76:70;;142:1;139;132:12;157:336;239:6;247;300:2;288:9;279:7;275:23;271:32;268:52;;;316:1;313;306:12;268:52;355:9;342:23;374:38;406:5;374:38;:::i;:::-;431:5;483:2;468:18;;;;455:32;;-1:-1:-1;;;157:336:25:o;498:391::-;585:8;595:6;649:3;642:4;634:6;630:17;626:27;616:55;;667:1;664;657:12;616:55;-1:-1:-1;690:20:25;;733:18;722:30;;719:50;;;765:1;762;755:12;719:50;802:4;794:6;790:17;778:29;;862:3;855:4;845:6;842:1;838:14;830:6;826:27;822:38;819:47;816:67;;;879:1;876;869:12;816:67;498:391;;;;;:::o;894:141::-;962:20;;991:38;962:20;991:38;:::i;:::-;894:141;;;:::o;1040:118::-;1126:5;1119:13;1112:21;1105:5;1102:32;1092:60;;1148:1;1145;1138:12;1163:773;1299:6;1307;1315;1323;1376:2;1364:9;1355:7;1351:23;1347:32;1344:52;;;1392:1;1389;1382:12;1344:52;1432:9;1419:23;1465:18;1457:6;1454:30;1451:50;;;1497:1;1494;1487:12;1451:50;1536:94;1622:7;1613:6;1602:9;1598:22;1536:94;:::i;:::-;1649:8;;-1:-1:-1;1510:120:25;-1:-1:-1;;1734:2:25;1719:18;;1706:32;1747:38;1706:32;1747:38;:::i;:::-;1804:5;-1:-1:-1;1861:2:25;1846:18;;1833:32;1874:30;1833:32;1874:30;:::i;:::-;1163:773;;;;-1:-1:-1;1163:773:25;;-1:-1:-1;;1163:773:25:o;1941:1168::-;2056:6;2064;2072;2080;2088;2096;2104;2157:3;2145:9;2136:7;2132:23;2128:33;2125:53;;;2174:1;2171;2164:12;2125:53;2213:9;2200:23;2232:38;2264:5;2232:38;:::i;:::-;2289:5;-1:-1:-1;2346:2:25;2331:18;;2318:32;2359:40;2318:32;2359:40;:::i;:::-;2418:7;-1:-1:-1;2477:2:25;2462:18;;2449:32;2490:40;2449:32;2490:40;:::i;:::-;2549:7;-1:-1:-1;2603:2:25;2588:18;;2575:32;;-1:-1:-1;2654:3:25;2639:19;;2626:33;;-1:-1:-1;2710:3:25;2695:19;;2682:33;2734:18;2764:14;;;2761:34;;;2791:1;2788;2781:12;2761:34;2829:6;2818:9;2814:22;2804:32;;2874:7;2867:4;2863:2;2859:13;2855:27;2845:55;;2896:1;2893;2886:12;2845:55;2936:2;2923:16;2962:2;2954:6;2951:14;2948:34;;;2978:1;2975;2968:12;2948:34;3023:7;3018:2;3009:6;3005:2;3001:15;2997:24;2994:37;2991:57;;;3044:1;3041;3034:12;2991:57;3075:2;3071;3067:11;3057:21;;3097:6;3087:16;;;;;1941:1168;;;;;;;;;;:::o;3306:164::-;3374:5;3419:3;3410:6;3405:3;3401:16;3397:26;3394:46;;;3436:1;3433;3426:12;3394:46;-1:-1:-1;3458:6:25;3306:164;-1:-1:-1;3306:164:25:o;3475:398::-;3578:6;3586;3639:3;3627:9;3618:7;3614:23;3610:33;3607:53;;;3656:1;3653;3646:12;3607:53;3679:62;3733:7;3722:9;3679:62;:::i;:::-;3669:72;;3791:3;3780:9;3776:19;3763:33;3805:38;3837:5;3805:38;:::i;:::-;3862:5;3852:15;;;3475:398;;;;;:::o;3878:272::-;3955:6;4008:2;3996:9;3987:7;3983:23;3979:32;3976:52;;;4024:1;4021;4014:12;3976:52;4063:9;4050:23;4082:38;4114:5;4082:38;:::i;4472:696::-;4593:6;4601;4609;4617;4670:3;4658:9;4649:7;4645:23;4641:33;4638:53;;;4687:1;4684;4677:12;4638:53;4710:62;4764:7;4753:9;4710:62;:::i;:::-;4700:72;;4822:3;4811:9;4807:19;4794:33;4836:38;4868:5;4836:38;:::i;:::-;4893:5;-1:-1:-1;4950:3:25;4935:19;;4922:33;4964:40;4922:33;4964:40;:::i;:::-;5023:7;-1:-1:-1;5082:3:25;5067:19;;5054:33;5096:40;5054:33;5096:40;:::i;5173:626::-;5270:6;5278;5331:2;5319:9;5310:7;5306:23;5302:32;5299:52;;;5347:1;5344;5337:12;5299:52;5387:9;5374:23;5416:18;5457:2;5449:6;5446:14;5443:34;;;5473:1;5470;5463:12;5443:34;5511:6;5500:9;5496:22;5486:32;;5556:7;5549:4;5545:2;5541:13;5537:27;5527:55;;5578:1;5575;5568:12;5527:55;5618:2;5605:16;5644:2;5636:6;5633:14;5630:34;;;5660:1;5657;5650:12;5630:34;5713:7;5708:2;5698:6;5695:1;5691:14;5687:2;5683:23;5679:32;5676:45;5673:65;;;5734:1;5731;5724:12;5673:65;5765:2;5757:11;;;;;5787:6;;-1:-1:-1;5173:626:25;;-1:-1:-1;;;;5173:626:25:o;5804:250::-;5889:1;5899:113;5913:6;5910:1;5907:13;5899:113;;;5989:11;;;5983:18;5970:11;;;5963:39;5935:2;5928:10;5899:113;;;-1:-1:-1;;6046:1:25;6028:16;;6021:27;5804:250::o;6059:270::-;6100:3;6138:5;6132:12;6165:6;6160:3;6153:19;6181:76;6250:6;6243:4;6238:3;6234:14;6227:4;6220:5;6216:16;6181:76;:::i;:::-;6311:2;6290:15;-1:-1:-1;;6286:29:25;6277:39;;;;6318:4;6273:50;;6059:270;-1:-1:-1;;6059:270:25:o;6334:800::-;6494:4;6523:2;6563;6552:9;6548:18;6593:2;6582:9;6575:21;6616:6;6651;6645:13;6682:6;6674;6667:22;6720:2;6709:9;6705:18;6698:25;;6782:2;6772:6;6769:1;6765:14;6754:9;6750:30;6746:39;6732:53;;6820:2;6812:6;6808:15;6841:1;6851:254;6865:6;6862:1;6859:13;6851:254;;;6958:2;6954:7;6942:9;6934:6;6930:22;6926:36;6921:3;6914:49;6986:39;7018:6;7009;7003:13;6986:39;:::i;:::-;6976:49;-1:-1:-1;7083:12:25;;;;7048:15;;;;6887:1;6880:9;6851:254;;;-1:-1:-1;7122:6:25;;6334:800;-1:-1:-1;;;;;;;6334:800:25:o;7139:1071::-;7293:6;7301;7309;7317;7325;7333;7386:3;7374:9;7365:7;7361:23;7357:33;7354:53;;;7403:1;7400;7393:12;7354:53;7443:9;7430:23;7476:18;7468:6;7465:30;7462:50;;;7508:1;7505;7498:12;7462:50;7547:94;7633:7;7624:6;7613:9;7609:22;7547:94;:::i;:::-;7660:8;;-1:-1:-1;7521:120:25;-1:-1:-1;;7745:2:25;7730:18;;7717:32;7758:38;7717:32;7758:38;:::i;:::-;7815:5;-1:-1:-1;7872:2:25;7857:18;;7844:32;7885:40;7844:32;7885:40;:::i;:::-;7944:7;-1:-1:-1;8003:2:25;7988:18;;7975:32;8016:40;7975:32;8016:40;:::i;:::-;8075:7;-1:-1:-1;8134:3:25;8119:19;;8106:33;8148:30;8106:33;8148:30;:::i;:::-;8197:7;8187:17;;;7139:1071;;;;;;;;:::o;8700:336::-;8902:2;8884:21;;;8941:2;8921:18;;;8914:30;-1:-1:-1;;;8975:2:25;8960:18;;8953:42;9027:2;9012:18;;8700:336::o;9041:184::-;9111:6;9164:2;9152:9;9143:7;9139:23;9135:32;9132:52;;;9180:1;9177;9170:12;9132:52;-1:-1:-1;9203:16:25;;9041:184;-1:-1:-1;9041:184:25:o;9230:127::-;9291:10;9286:3;9282:20;9279:1;9272:31;9322:4;9319:1;9312:15;9346:4;9343:1;9336:15;9362:237;9443:1;9436:5;9433:12;9423:143;;9488:10;9483:3;9479:20;9476:1;9469:31;9523:4;9520:1;9513:15;9551:4;9548:1;9541:15;9423:143;9575:18;;9362:237::o;9604:570::-;9850:3;9835:19;;9863:44;9839:9;9889:6;9863:44;:::i;:::-;-1:-1:-1;;;;;9981:15:25;;;9976:2;9961:18;;9954:43;10033:15;;;;10028:2;10013:18;;10006:43;10092:14;;10085:22;10080:2;10065:18;;10058:50;10152:14;10145:22;10139:3;10124:19;;;10117:51;;;;9604:570;;-1:-1:-1;9604:570:25:o;10179:179::-;10246:20;;-1:-1:-1;;;;;10295:38:25;;10285:49;;10275:77;;10348:1;10345;10338:12;10363:188;10431:20;;-1:-1:-1;;;;;10480:46:25;;10470:57;;10460:85;;10541:1;10538;10531:12;10680:165;10747:20;;10807:12;10796:24;;10786:35;;10776:63;;10835:1;10832;10825:12;10951:161;11018:20;;11078:8;11067:20;;11057:31;;11047:59;;11102:1;11099;11092:12;11214:2092;11537:2;11589:21;;;11562:18;;;11645:22;;;11508:4;;11686:3;11705:18;;;11746:6;11508:4;11780:1359;11794:6;11791:1;11788:13;11780:1359;;;-1:-1:-1;;;;;11859:25:25;11877:6;11859:25;:::i;:::-;11855:58;11850:3;11843:71;11937:4;11992:2;11984:6;11980:15;11967:29;12009:38;12041:5;12009:38;:::i;:::-;-1:-1:-1;;;;;12081:31:25;12067:12;;;12060:53;12136:4;12181:15;;;12168:29;12210:40;12168:29;12210:40;:::i;:::-;-1:-1:-1;;;;;4221:31:25;12291:12;;;4209:44;12337:35;12356:15;;;12337:35;:::i;:::-;-1:-1:-1;;;;;10622:46:25;12418:12;;;10610:59;12466:35;12485:15;;;12466:35;:::i;:::-;-1:-1:-1;;;;;10622:46:25;12549:12;;;10610:59;12585:4;12624:34;12642:15;;;12624:34;:::i;:::-;10926:12;10915:24;12705:12;;;10903:37;12741:4;12780:34;12798:15;;;12780:34;:::i;:::-;11193:8;11182:20;12861:12;;;11170:33;12897:4;12936:34;12954:15;;;12936:34;:::i;:::-;11193:8;11182:20;13017:12;;;11170:33;13053:6;13079:12;;;;13114:15;;;;;11816:1;11809:9;11780:1359;;;11784:3;;13186:9;13181:3;13177:19;13170:4;13159:9;13155:20;13148:49;13214:29;13239:3;13231:6;13214:29;:::i;:::-;13206:37;;;;;13252:48;13294:4;13283:9;13279:20;13271:6;-1:-1:-1;;;;;4221:31:25;4209:44;;4155:104;13311:150;13386:20;;13435:1;13425:12;;13415:40;;13451:1;13448;13441:12;13466:207;13539:6;13592:2;13580:9;13571:7;13567:23;13563:32;13560:52;;;13608:1;13605;13598:12;13560:52;13631:36;13657:9;13631:36;:::i;13678:127::-;13739:10;13734:3;13730:20;13727:1;13720:31;13770:4;13767:1;13760:15;13794:4;13791:1;13784:15;13810:1079;13902:6;13933:3;13977:2;13965:9;13956:7;13952:23;13948:32;13945:52;;;13993:1;13990;13983:12;13945:52;14026:2;14020:9;14056:15;;;;14101:18;14086:34;;14122:22;;;14083:62;14080:185;;;14187:10;14182:3;14178:20;14175:1;14168:31;14222:4;14219:1;14212:15;14250:4;14247:1;14240:15;14080:185;14285:10;14281:2;14274:22;14320:28;14338:9;14320:28;:::i;:::-;14312:6;14305:44;14382:38;14416:2;14405:9;14401:18;14382:38;:::i;:::-;14377:2;14369:6;14365:15;14358:63;14454:38;14488:2;14477:9;14473:18;14454:38;:::i;:::-;14449:2;14441:6;14437:15;14430:63;14526:38;14560:2;14549:9;14545:18;14526:38;:::i;:::-;14521:2;14513:6;14509:15;14502:63;14599:39;14633:3;14622:9;14618:19;14599:39;:::i;:::-;14593:3;14585:6;14581:16;14574:65;14673:38;14706:3;14695:9;14691:19;14673:38;:::i;:::-;14667:3;14659:6;14655:16;14648:64;14746:38;14779:3;14768:9;14764:19;14746:38;:::i;:::-;14740:3;14732:6;14728:16;14721:64;14819:38;14852:3;14841:9;14837:19;14819:38;:::i;:::-;14813:3;14801:16;;14794:64;14805:6;13810:1079;-1:-1:-1;;;;13810:1079:25:o;14894:127::-;14955:10;14950:3;14946:20;14943:1;14936:31;14986:4;14983:1;14976:15;15010:4;15007:1;15000:15;15026:1911;15337:2;15389:21;;;15459:13;;15362:18;;;15481:22;;;15308:4;;15337:2;15522:3;;15541:18;;;;15578:4;15605:15;;;15308:4;15648:1124;15662:6;15659:1;15656:13;15648:1124;;;15721:13;;15763:9;;-1:-1:-1;;;;;15759:42:25;15747:55;;15841:11;;;15835:18;-1:-1:-1;;;;;15929:21:25;;;15915:12;;;15908:43;15974:4;16022:11;;;16016:18;16012:27;;;15998:12;;;15991:49;16081:11;;;16075:18;-1:-1:-1;;;;;10622:46:25;;;16141:12;;;10610:59;16195:11;;;16189:18;10622:46;16255:12;;;10610:59;16291:4;16336:11;;;16330:18;10926:12;10915:24;16395:12;;;10903:37;16431:4;16476:11;;;16470:18;11193:8;11182:20;;;16535:12;;;11170:33;;;;16571:4;16616:11;;;16610:18;11182:20;16675:12;;;11170:33;16717:6;16708:16;;;;16747:15;;;;15684:1;15677:9;15648:1124;;;-1:-1:-1;;16808:19:25;;;16788:18;;;16781:47;-1:-1:-1;16845:29:25;16812:3;16862:6;16845:29;:::i;:::-;16837:37;;;;;16883:48;16925:4;16914:9;16910:20;16902:6;-1:-1:-1;;;;;4221:31:25;4209:44;;4155:104;16942:400;-1:-1:-1;;;;;17198:15:25;;;17180:34;;17250:15;;;;17245:2;17230:18;;17223:43;-1:-1:-1;;;;;;17302:33:25;;;17297:2;17282:18;;17275:61;17130:2;17115:18;;16942:400::o;17347:245::-;17414:6;17467:2;17455:9;17446:7;17442:23;17438:32;17435:52;;;17483:1;17480;17473:12;17435:52;17515:9;17509:16;17534:28;17556:5;17534:28;:::i;17597:732::-;17899:3;17884:19;;17912:44;17888:9;17938:6;17912:44;:::i;:::-;-1:-1:-1;;;;;18030:15:25;;;18025:2;18010:18;;18003:43;18082:15;;;18077:2;18062:18;;18055:43;18134:15;;;18129:2;18114:18;;18107:43;18187:15;;;;18181:3;18166:19;;18159:44;18247:14;;18240:22;17983:3;18219:19;;18212:51;18307:14;;18300:22;18294:3;18279:19;;;18272:51;;;;17597:732;;-1:-1:-1;17597:732:25:o;18334:127::-;18395:10;18390:3;18386:20;18383:1;18376:31;18426:4;18423:1;18416:15;18450:4;18447:1;18440:15;18466:128;18533:9;;;18554:11;;;18551:37;;;18568:18;;:::i;18935:521::-;19012:4;19018:6;19078:11;19065:25;19172:2;19168:7;19157:8;19141:14;19137:29;19133:43;19113:18;19109:68;19099:96;;19191:1;19188;19181:12;19099:96;19218:33;;19270:20;;;-1:-1:-1;19313:18:25;19302:30;;19299:50;;;19345:1;19342;19335:12;19299:50;19378:4;19366:17;;-1:-1:-1;19409:14:25;19405:27;;;19395:38;;19392:58;;;19446:1;19443;19436:12;19461:440;19690:6;19682;19677:3;19664:33;19646:3;19725:6;19720:3;19716:16;19752:1;19748:2;19741:13;19783:6;19777:13;19799:65;19857:6;19853:2;19846:4;19838:6;19834:17;19799:65;:::i;:::-;19880:15;;19461:440;-1:-1:-1;;;;;19461:440:25:o;19906:135::-;19945:3;19966:17;;;19963:43;;19986:18;;:::i;:::-;-1:-1:-1;20033:1:25;20022:13;;19906:135::o;20390:813::-;20537:6;20545;20553;20561;20569;20622:3;20610:9;20601:7;20597:23;20593:33;20590:53;;;20639:1;20636;20629:12;20590:53;20662:36;20688:9;20662:36;:::i;:::-;20652:46;;20748:2;20737:9;20733:18;20720:32;20761:38;20793:5;20761:38;:::i;:::-;20818:5;-1:-1:-1;20875:2:25;20860:18;;20847:32;20888:40;20847:32;20888:40;:::i;:::-;20947:7;-1:-1:-1;21006:2:25;20991:18;;20978:32;21019:30;20978:32;21019:30;:::i;:::-;21068:7;-1:-1:-1;21127:3:25;21112:19;;21099:33;21141:30;21099:33;21141:30;:::i;:::-;21190:7;21180:17;;;20390:813;;;;;;;;:::o;21208:278::-;21298:6;21351:2;21339:9;21330:7;21326:23;21322:32;21319:52;;;21367:1;21364;21357:12;21319:52;21399:9;21393:16;21418:38;21450:5;21418:38;:::i;21800:469::-;-1:-1:-1;;;;;22109:15:25;;;22091:34;;22156:2;22141:18;;22134:34;;;;22199:2;22184:18;;22177:34;;;;22247:15;;;22242:2;22227:18;;22220:43;22040:3;22025:19;;21800:469::o;22456:1161::-;22671:6;22679;22687;22695;22703;22711;22719;22772:3;22760:9;22751:7;22747:23;22743:33;22740:53;;;22789:1;22786;22779:12;22740:53;22812:36;22838:9;22812:36;:::i;:::-;22802:46;;22898:2;22887:9;22883:18;22870:32;22911:38;22943:5;22911:38;:::i;:::-;22968:5;-1:-1:-1;23025:2:25;23010:18;;22997:32;23038:40;22997:32;23038:40;:::i;:::-;23097:7;-1:-1:-1;23156:2:25;23141:18;;23128:32;23169:40;23128:32;23169:40;:::i;:::-;23228:7;-1:-1:-1;23287:3:25;23272:19;;23259:33;23301:40;23259:33;23301:40;:::i;:::-;23360:7;-1:-1:-1;23419:3:25;23404:19;;23391:33;23433:30;23391:33;23433:30;:::i;:::-;23482:7;-1:-1:-1;23541:3:25;23526:19;;23513:33;23555:30;23513:33;23555:30;:::i;:::-;23604:7;23594:17;;;22456:1161;;;;;;;;;;:::o;24609:287::-;24738:3;24776:6;24770:13;24792:66;24851:6;24846:3;24839:4;24831:6;24827:17;24792:66;:::i;:::-;24874:16;;;;;24609:287;-1:-1:-1;;24609:287:25:o
Swarm Source
ipfs://6e8ea341023cedbde5f12cd60c95b72da9cb49a98f11c49d49585ec08b5cbaad
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.