Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Contract Name:
AuctionLib
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 800 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {SafeBox, CollectionState, AuctionInfo, FeeConfig, Fees, FeeRate, AuctionType} from "./Structs.sol"; import "./User.sol"; import "./Collection.sol"; import "./Helper.sol"; import "../Errors.sol"; import "../Constants.sol"; import "../interface/IFlooring.sol"; import {SafeBoxLib} from "./SafeBox.sol"; import "../library/RollingBuckets.sol"; library AuctionLib { using SafeCast for uint256; using CollectionLib for CollectionState; using SafeBoxLib for SafeBox; using RollingBuckets for mapping(uint256 => uint256); using UserLib for UserFloorAccount; using UserLib for CollectionAccount; using Helper for CollectionState; event AuctionStartedV2( address indexed trigger, address indexed collection, uint64[] activityIds, uint256[] tokenIds, AuctionType typ, Fees fees, address settleToken, uint256 minimumBid, uint256 auctionEndTime, uint256 safeBoxExpiryTs, uint256 adminFee ); event NewTopBidOnAuction( address indexed bidder, address indexed collection, uint64 activityId, uint256 tokenId, uint256 bidAmount, uint256 auctionEndTime, uint256 safeBoxExpiryTs ); event AuctionEnded( address indexed winner, address indexed collection, uint64 activityId, uint256 tokenId, uint256 safeBoxKeyId, uint256 collectedFunds ); function ownerInitAuctions( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, address collectionId, uint256[] memory nftIds, uint256 maxExpiry, address token, uint256 minimumBid ) public { if (feeConf.safeboxFee.receipt == address(0)) revert Errors.TokenNotSupported(); UserFloorAccount storage userAccount = userAccounts[msg.sender]; uint256 adminFee = Constants.AUCTION_COST * nftIds.length; /// transfer fee to contract account userAccount.transferToken(userAccounts[address(this)], creditToken, adminFee, true); AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = token; auctionTemplate.minimumBid = minimumBid.toUint96(); auctionTemplate.triggerAddress = msg.sender; auctionTemplate.lastBidAmount = 0; auctionTemplate.lastBidder = address(0); auctionTemplate.typ = AuctionType.Owned; auctionTemplate.fees = Fees({ royalty: FeeRate({receipt: address(0), rateBips: 0}), protocol: FeeRate({receipt: feeConf.safeboxFee.receipt, rateBips: feeConf.safeboxFee.auctionOwned}) }); (uint64[] memory activityIds, uint192 newExpiryTs) = _ownerInitAuctions(collection, userAccount.getByKey(collectionId), nftIds, maxExpiry, auctionTemplate); emit AuctionStartedV2( msg.sender, collectionId, activityIds, nftIds, auctionTemplate.typ, auctionTemplate.fees, token, minimumBid, auctionTemplate.endTime, newExpiryTs, adminFee ); } function _ownerInitAuctions( CollectionState storage collectionState, CollectionAccount storage userAccount, uint256[] memory nftIds, uint256 maxExpiry, AuctionInfo memory auctionTemplate ) private returns (uint64[] memory activityIds, uint32 newExpiryTs) { newExpiryTs = uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS); uint256 firstIdx = Helper.counterStamp(newExpiryTs) - Helper.counterStamp(block.timestamp); uint256[] memory toUpdateBucket; /// if maxExpiryTs == 0, it means all nftIds in this batch being locked infinitely that we don't need to update countingBuckets if (maxExpiry > 0) { toUpdateBucket = collectionState.countingBuckets.batchGet( Helper.counterStamp(block.timestamp), Math.min(Helper.counterStamp(maxExpiry), collectionState.lastUpdatedBucket) ); } activityIds = new uint64[](nftIds.length); for (uint256 i = 0; i < nftIds.length;) { if (collectionState.hasActiveActivities(nftIds[i])) revert Errors.NftHasActiveActivities(); (SafeBox storage safeBox,) = collectionState.useSafeBoxAndKey(userAccount, nftIds[i]); if (safeBox.isInfiniteSafeBox()) { --collectionState.infiniteCnt; } else { uint256 oldExpiryTs = safeBox.expiryTs; if (oldExpiryTs < newExpiryTs) { revert Errors.InvalidParam(); } uint256 lastIdx = Helper.counterStamp(oldExpiryTs) - Helper.counterStamp(block.timestamp); if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length) revert Errors.InvalidParam(); for (uint256 k = firstIdx; k < lastIdx;) { --toUpdateBucket[k]; unchecked { ++k; } } } safeBox.expiryTs = newExpiryTs; activityIds[i] = collectionState.generateNextActivityId(); auctionTemplate.activityId = activityIds[i]; collectionState.activeAuctions[nftIds[i]] = auctionTemplate; unchecked { ++i; } } if (toUpdateBucket.length > 0) { collectionState.countingBuckets.batchSet(Helper.counterStamp(block.timestamp), toUpdateBucket); } } function initAuctionOnExpiredSafeBoxes( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, address collectionId, uint256[] memory nftIds, address bidToken, uint256 bidAmount ) public { if (feeConf.safeboxFee.receipt == address(0)) revert Errors.TokenNotSupported(); { uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0); (uint256 currentFee,) = Constants.getVaultFeeAtLR(lockingRatio); if (bidAmount < currentFee) revert Errors.AuctionInvalidBidAmount(); } AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = bidToken; auctionTemplate.minimumBid = bidAmount.toUint96(); auctionTemplate.triggerAddress = msg.sender; auctionTemplate.lastBidAmount = bidAmount.toUint96(); auctionTemplate.lastBidder = msg.sender; auctionTemplate.typ = AuctionType.Expired; auctionTemplate.fees = Fees({ royalty: FeeRate({receipt: address(0), rateBips: 0}), protocol: FeeRate({receipt: feeConf.safeboxFee.receipt, rateBips: feeConf.safeboxFee.auctionExpired}) }); (uint64[] memory activityIds, uint192 newExpiry) = _initAuctionOnExpiredSafeBoxes(collection, nftIds, auctionTemplate); uint256 adminFee = Constants.AUCTION_ON_EXPIRED_SAFEBOX_COST * nftIds.length; if (bidToken == creditToken) { userAccounts[msg.sender].transferToken( userAccounts[address(this)], bidToken, bidAmount * nftIds.length + adminFee, true ); } else { userAccounts[msg.sender].transferToken( userAccounts[address(this)], bidToken, bidAmount * nftIds.length, false ); if (adminFee > 0) { userAccounts[msg.sender].transferToken(userAccounts[address(this)], creditToken, adminFee, true); } } emit AuctionStartedV2( msg.sender, collectionId, activityIds, nftIds, auctionTemplate.typ, auctionTemplate.fees, bidToken, bidAmount, auctionTemplate.endTime, newExpiry, adminFee ); } function _initAuctionOnExpiredSafeBoxes( CollectionState storage collectionState, uint256[] memory nftIds, AuctionInfo memory auctionTemplate ) private returns (uint64[] memory activityIds, uint32 newExpiry) { newExpiry = uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS); activityIds = new uint64[](nftIds.length); for (uint256 idx; idx < nftIds.length;) { uint256 nftId = nftIds[idx]; if (collectionState.hasActiveActivities(nftId)) revert Errors.NftHasActiveActivities(); SafeBox storage safeBox = collectionState.useSafeBox(nftId); if (!safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasNotExpire(); if (Helper.isAuctionPeriodOver(safeBox)) revert Errors.SafeBoxAuctionWindowHasPassed(); activityIds[idx] = collectionState.generateNextActivityId(); auctionTemplate.activityId = activityIds[idx]; collectionState.activeAuctions[nftId] = auctionTemplate; /// We keep the owner of safebox unchanged, and it will be used to distribute auction funds safeBox.expiryTs = newExpiry; safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION; unchecked { ++idx; } } Helper.applyDiffToCounters( collectionState, Helper.counterStamp(block.timestamp), Helper.counterStamp(newExpiry), int256(nftIds.length) ); } function initAuctionOnVault( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, FeeConfig storage feeConf, address creditToken, address collectionId, uint256[] memory vaultIdx, address bidToken, uint96 bidAmount ) public { if (vaultIdx.length != 1) revert Errors.InvalidParam(); if (feeConf.vaultFee.receipt == address(0)) revert Errors.TokenNotSupported(); { /// check auction period and bid price uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0); uint256 periodDuration = Constants.getVaultAuctionDurationAtLR(lockingRatio); if (block.timestamp - collection.lastVaultAuctionPeriodTs <= periodDuration) { revert Errors.PeriodQuotaExhausted(); } (uint256 currentFee,) = Constants.getVaultFeeAtLR(lockingRatio); if (bidAmount < currentFee) revert Errors.AuctionInvalidBidAmount(); } AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = bidToken; auctionTemplate.minimumBid = bidAmount; auctionTemplate.triggerAddress = msg.sender; auctionTemplate.lastBidAmount = bidAmount; auctionTemplate.lastBidder = msg.sender; auctionTemplate.typ = AuctionType.Vault; auctionTemplate.fees = Fees({ royalty: FeeRate({receipt: feeConf.royalty.receipt, rateBips: feeConf.royalty.vault}), protocol: FeeRate({receipt: feeConf.vaultFee.receipt, rateBips: feeConf.vaultFee.vaultAuction}) }); SafeBox memory safeboxTemplate = SafeBox({ keyId: SafeBoxLib.SAFEBOX_KEY_NOTATION, expiryTs: uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS), owner: address(this) }); uint64[] memory activityIds = new uint64[](vaultIdx.length); /// vaultIdx keeps asc order for (uint256 i = vaultIdx.length; i > 0;) { unchecked { --i; } if (vaultIdx[i] >= collection.freeTokenIds.length) revert Errors.InvalidParam(); uint256 nftId = collection.freeTokenIds[vaultIdx[i]]; collection.addSafeBox(nftId, safeboxTemplate); auctionTemplate.activityId = collection.generateNextActivityId(); collection.activeAuctions[nftId] = auctionTemplate; activityIds[i] = auctionTemplate.activityId; collection.freeTokenIds[vaultIdx[i]] = collection.freeTokenIds[collection.freeTokenIds.length - 1]; collection.freeTokenIds.pop(); /// reuse the array vaultIdx[i] = nftId; } userAccounts[msg.sender].transferToken( userAccounts[address(this)], auctionTemplate.bidTokenAddress, bidAmount * vaultIdx.length, bidToken == creditToken ); Helper.applyDiffToCounters( collection, Helper.counterStamp(block.timestamp), Helper.counterStamp(safeboxTemplate.expiryTs), int256(vaultIdx.length) ); /// update auction timestamp collection.lastVaultAuctionPeriodTs = uint32(block.timestamp); emit AuctionStartedV2( msg.sender, collectionId, activityIds, vaultIdx, auctionTemplate.typ, auctionTemplate.fees, auctionTemplate.bidTokenAddress, bidAmount, auctionTemplate.endTime, safeboxTemplate.expiryTs, 0 ); } struct BidParam { uint256 nftId; uint96 bidAmount; address bidder; uint256 extendDuration; uint256 minIncrPct; } function placeBidOnAuction( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address creditToken, address collectionId, uint256 nftId, uint256 bidAmount, uint256 bidOptionIdx ) public { uint256 prevBidAmount; address prevBidder; { Constants.AuctionBidOption memory bidOption = Constants.getBidOption(bidOptionIdx); userAccounts[msg.sender].ensureVipCredit(uint8(bidOption.vipLevel), creditToken); (prevBidAmount, prevBidder) = _placeBidOnAuction( collection, BidParam( nftId, bidAmount.toUint96(), msg.sender, bidOption.extendDurationSecs, bidOption.minimumRaisePct ) ); } AuctionInfo memory auction = collection.activeAuctions[nftId]; address bidToken = auction.bidTokenAddress; userAccounts[msg.sender].transferToken( userAccounts[address(this)], bidToken, bidAmount, bidToken == creditToken ); if (prevBidAmount > 0) { /// refund previous bid /// contract account no need to check credit requirements userAccounts[address(this)].transferToken(userAccounts[prevBidder], bidToken, prevBidAmount, false); } SafeBox memory safebox = collection.safeBoxes[nftId]; emit NewTopBidOnAuction( msg.sender, collectionId, auction.activityId, nftId, bidAmount, auction.endTime, safebox.expiryTs ); } function _placeBidOnAuction(CollectionState storage collectionState, BidParam memory param) private returns (uint128 prevBidAmount, address prevBidder) { AuctionInfo storage auctionInfo = collectionState.activeAuctions[param.nftId]; SafeBox storage safeBox = collectionState.useSafeBox(param.nftId); uint256 endTime = auctionInfo.endTime; { (prevBidAmount, prevBidder) = (auctionInfo.lastBidAmount, auctionInfo.lastBidder); // param check if (endTime == 0) revert Errors.AuctionNotExist(); if (endTime <= block.timestamp) revert Errors.AuctionHasExpire(); if (prevBidAmount >= param.bidAmount || auctionInfo.minimumBid > param.bidAmount) { revert Errors.AuctionBidIsNotHighEnough(); } if (prevBidder == param.bidder) revert Errors.AuctionSelfBid(); // owner starts auction, can not bid by himself if (auctionInfo.typ == AuctionType.Owned && param.bidder == safeBox.owner) revert Errors.AuctionSelfBid(); if (prevBidAmount > 0 && !isValidNewBid(param.bidAmount, prevBidAmount, param.minIncrPct)) { revert Errors.AuctionInvalidBidAmount(); } } /// Changing safebox key id which means the corresponding safebox key doesn't hold the safebox now safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION; uint256 newAuctionEndTime = block.timestamp + param.extendDuration; if (newAuctionEndTime > endTime) { uint256 newSafeBoxExpiryTs = newAuctionEndTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS; Helper.applyDiffToCounters( collectionState, Helper.counterStamp(safeBox.expiryTs), Helper.counterStamp(newSafeBoxExpiryTs), 1 ); safeBox.expiryTs = uint32(newSafeBoxExpiryTs); auctionInfo.endTime = uint96(newAuctionEndTime); } auctionInfo.lastBidAmount = param.bidAmount; auctionInfo.lastBidder = param.bidder; } function settleAuctions( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address collectionId, uint256[] memory nftIds ) public { for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; SafeBox storage safebox = Helper.useSafeBox(collection, nftId); if (safebox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire(); AuctionInfo memory auctionInfo = collection.activeAuctions[nftId]; if (auctionInfo.endTime == 0) revert Errors.AuctionNotExist(); if (auctionInfo.endTime > block.timestamp) revert Errors.AuctionHasNotCompleted(); /// noone bid on the aciton, can not be settled if (auctionInfo.lastBidder == address(0)) revert Errors.AuctionHasNotCompleted(); distributeFunds(userAccounts, auctionInfo, safebox.owner); /// transfer safebox address winner = auctionInfo.lastBidder; SafeBoxKey memory key = SafeBoxKey({keyId: collection.generateNextKeyId(), lockingCredit: 0, vipLevel: 0}); safebox.keyId = key.keyId; safebox.owner = winner; UserFloorAccount storage account = userAccounts[winner]; CollectionAccount storage userCollectionAccount = account.getByKey(collectionId); userCollectionAccount.addSafeboxKey(nftId, key); delete collection.activeAuctions[nftId]; emit AuctionEnded(winner, collectionId, auctionInfo.activityId, nftId, key.keyId, auctionInfo.lastBidAmount); unchecked { ++i; } } } function distributeFunds( mapping(address => UserFloorAccount) storage accounts, AuctionInfo memory auction, address owner ) private { /// contract account no need to check credit requirements address token = auction.bidTokenAddress; Fees memory fees = auction.fees; (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) = Helper.computeFees(auction.lastBidAmount, fees); UserFloorAccount storage contractAccount = accounts[address(this)]; if (royalty > 0) { contractAccount.transferToken(accounts[fees.royalty.receipt], token, royalty, false); } if (protocolFee > 0) { contractAccount.transferToken(accounts[fees.protocol.receipt], token, protocolFee, false); } if (priceWithoutFee > 0) { contractAccount.transferToken(accounts[owner], token, priceWithoutFee, false); } } function isValidNewBid(uint256 newBid, uint256 previousBid, uint256 minRaisePct) private pure returns (bool) { uint256 minIncrement = previousBid * minRaisePct / 100; if (minIncrement < 1) { minIncrement = 1; } if (newBid < previousBid + minIncrement) { return false; } // think: always thought this should be previousBid.... uint256 newIncrementAmount = newBid / 100; if (newIncrementAmount < 1) { newIncrementAmount = 1; } return newBid % newIncrementAmount == 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon * a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or * {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon * a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the address zero. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.20; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be * reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { /** * @dev Muldiv operation overflow. */ error MathOverflowedMulDiv(); enum Rounding { Floor, // Toward negative infinity Ceil, // Toward positive infinity Trunc, // Toward zero Expand // Away from zero } /** * @dev Returns the addition of two unsigned integers, with an overflow flag. */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an overflow flag. */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { // Guarantee the same behavior as in a regular Solidity division. return a / b; } // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or * denominator == 0. * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by * Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (denominator <= prod1) { revert MathOverflowedMulDiv(); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. uint256 twos = denominator & (0 - denominator); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also // works in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256 of a positive value rounded towards zero. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); } } /** * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. */ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { return uint8(rounding) % 2 == 1; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.20; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeCast { /** * @dev Value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); /** * @dev An int value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedIntToUint(int256 value); /** * @dev Value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); /** * @dev An uint value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedUintToInt(uint256 value); /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits */ function toUint248(uint256 value) internal pure returns (uint248) { if (value > type(uint248).max) { revert SafeCastOverflowedUintDowncast(248, value); } return uint248(value); } /** * @dev Returns the downcasted uint240 from uint256, reverting on * overflow (when the input is greater than largest uint240). * * Counterpart to Solidity's `uint240` operator. * * Requirements: * * - input must fit into 240 bits */ function toUint240(uint256 value) internal pure returns (uint240) { if (value > type(uint240).max) { revert SafeCastOverflowedUintDowncast(240, value); } return uint240(value); } /** * @dev Returns the downcasted uint232 from uint256, reverting on * overflow (when the input is greater than largest uint232). * * Counterpart to Solidity's `uint232` operator. * * Requirements: * * - input must fit into 232 bits */ function toUint232(uint256 value) internal pure returns (uint232) { if (value > type(uint232).max) { revert SafeCastOverflowedUintDowncast(232, value); } return uint232(value); } /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). * * Counterpart to Solidity's `uint224` operator. * * Requirements: * * - input must fit into 224 bits */ function toUint224(uint256 value) internal pure returns (uint224) { if (value > type(uint224).max) { revert SafeCastOverflowedUintDowncast(224, value); } return uint224(value); } /** * @dev Returns the downcasted uint216 from uint256, reverting on * overflow (when the input is greater than largest uint216). * * Counterpart to Solidity's `uint216` operator. * * Requirements: * * - input must fit into 216 bits */ function toUint216(uint256 value) internal pure returns (uint216) { if (value > type(uint216).max) { revert SafeCastOverflowedUintDowncast(216, value); } return uint216(value); } /** * @dev Returns the downcasted uint208 from uint256, reverting on * overflow (when the input is greater than largest uint208). * * Counterpart to Solidity's `uint208` operator. * * Requirements: * * - input must fit into 208 bits */ function toUint208(uint256 value) internal pure returns (uint208) { if (value > type(uint208).max) { revert SafeCastOverflowedUintDowncast(208, value); } return uint208(value); } /** * @dev Returns the downcasted uint200 from uint256, reverting on * overflow (when the input is greater than largest uint200). * * Counterpart to Solidity's `uint200` operator. * * Requirements: * * - input must fit into 200 bits */ function toUint200(uint256 value) internal pure returns (uint200) { if (value > type(uint200).max) { revert SafeCastOverflowedUintDowncast(200, value); } return uint200(value); } /** * @dev Returns the downcasted uint192 from uint256, reverting on * overflow (when the input is greater than largest uint192). * * Counterpart to Solidity's `uint192` operator. * * Requirements: * * - input must fit into 192 bits */ function toUint192(uint256 value) internal pure returns (uint192) { if (value > type(uint192).max) { revert SafeCastOverflowedUintDowncast(192, value); } return uint192(value); } /** * @dev Returns the downcasted uint184 from uint256, reverting on * overflow (when the input is greater than largest uint184). * * Counterpart to Solidity's `uint184` operator. * * Requirements: * * - input must fit into 184 bits */ function toUint184(uint256 value) internal pure returns (uint184) { if (value > type(uint184).max) { revert SafeCastOverflowedUintDowncast(184, value); } return uint184(value); } /** * @dev Returns the downcasted uint176 from uint256, reverting on * overflow (when the input is greater than largest uint176). * * Counterpart to Solidity's `uint176` operator. * * Requirements: * * - input must fit into 176 bits */ function toUint176(uint256 value) internal pure returns (uint176) { if (value > type(uint176).max) { revert SafeCastOverflowedUintDowncast(176, value); } return uint176(value); } /** * @dev Returns the downcasted uint168 from uint256, reverting on * overflow (when the input is greater than largest uint168). * * Counterpart to Solidity's `uint168` operator. * * Requirements: * * - input must fit into 168 bits */ function toUint168(uint256 value) internal pure returns (uint168) { if (value > type(uint168).max) { revert SafeCastOverflowedUintDowncast(168, value); } return uint168(value); } /** * @dev Returns the downcasted uint160 from uint256, reverting on * overflow (when the input is greater than largest uint160). * * Counterpart to Solidity's `uint160` operator. * * Requirements: * * - input must fit into 160 bits */ function toUint160(uint256 value) internal pure returns (uint160) { if (value > type(uint160).max) { revert SafeCastOverflowedUintDowncast(160, value); } return uint160(value); } /** * @dev Returns the downcasted uint152 from uint256, reverting on * overflow (when the input is greater than largest uint152). * * Counterpart to Solidity's `uint152` operator. * * Requirements: * * - input must fit into 152 bits */ function toUint152(uint256 value) internal pure returns (uint152) { if (value > type(uint152).max) { revert SafeCastOverflowedUintDowncast(152, value); } return uint152(value); } /** * @dev Returns the downcasted uint144 from uint256, reverting on * overflow (when the input is greater than largest uint144). * * Counterpart to Solidity's `uint144` operator. * * Requirements: * * - input must fit into 144 bits */ function toUint144(uint256 value) internal pure returns (uint144) { if (value > type(uint144).max) { revert SafeCastOverflowedUintDowncast(144, value); } return uint144(value); } /** * @dev Returns the downcasted uint136 from uint256, reverting on * overflow (when the input is greater than largest uint136). * * Counterpart to Solidity's `uint136` operator. * * Requirements: * * - input must fit into 136 bits */ function toUint136(uint256 value) internal pure returns (uint136) { if (value > type(uint136).max) { revert SafeCastOverflowedUintDowncast(136, value); } return uint136(value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 value) internal pure returns (uint128) { if (value > type(uint128).max) { revert SafeCastOverflowedUintDowncast(128, value); } return uint128(value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits */ function toUint120(uint256 value) internal pure returns (uint120) { if (value > type(uint120).max) { revert SafeCastOverflowedUintDowncast(120, value); } return uint120(value); } /** * @dev Returns the downcasted uint112 from uint256, reverting on * overflow (when the input is greater than largest uint112). * * Counterpart to Solidity's `uint112` operator. * * Requirements: * * - input must fit into 112 bits */ function toUint112(uint256 value) internal pure returns (uint112) { if (value > type(uint112).max) { revert SafeCastOverflowedUintDowncast(112, value); } return uint112(value); } /** * @dev Returns the downcasted uint104 from uint256, reverting on * overflow (when the input is greater than largest uint104). * * Counterpart to Solidity's `uint104` operator. * * Requirements: * * - input must fit into 104 bits */ function toUint104(uint256 value) internal pure returns (uint104) { if (value > type(uint104).max) { revert SafeCastOverflowedUintDowncast(104, value); } return uint104(value); } /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). * * Counterpart to Solidity's `uint96` operator. * * Requirements: * * - input must fit into 96 bits */ function toUint96(uint256 value) internal pure returns (uint96) { if (value > type(uint96).max) { revert SafeCastOverflowedUintDowncast(96, value); } return uint96(value); } /** * @dev Returns the downcasted uint88 from uint256, reverting on * overflow (when the input is greater than largest uint88). * * Counterpart to Solidity's `uint88` operator. * * Requirements: * * - input must fit into 88 bits */ function toUint88(uint256 value) internal pure returns (uint88) { if (value > type(uint88).max) { revert SafeCastOverflowedUintDowncast(88, value); } return uint88(value); } /** * @dev Returns the downcasted uint80 from uint256, reverting on * overflow (when the input is greater than largest uint80). * * Counterpart to Solidity's `uint80` operator. * * Requirements: * * - input must fit into 80 bits */ function toUint80(uint256 value) internal pure returns (uint80) { if (value > type(uint80).max) { revert SafeCastOverflowedUintDowncast(80, value); } return uint80(value); } /** * @dev Returns the downcasted uint72 from uint256, reverting on * overflow (when the input is greater than largest uint72). * * Counterpart to Solidity's `uint72` operator. * * Requirements: * * - input must fit into 72 bits */ function toUint72(uint256 value) internal pure returns (uint72) { if (value > type(uint72).max) { revert SafeCastOverflowedUintDowncast(72, value); } return uint72(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 value) internal pure returns (uint64) { if (value > type(uint64).max) { revert SafeCastOverflowedUintDowncast(64, value); } return uint64(value); } /** * @dev Returns the downcasted uint56 from uint256, reverting on * overflow (when the input is greater than largest uint56). * * Counterpart to Solidity's `uint56` operator. * * Requirements: * * - input must fit into 56 bits */ function toUint56(uint256 value) internal pure returns (uint56) { if (value > type(uint56).max) { revert SafeCastOverflowedUintDowncast(56, value); } return uint56(value); } /** * @dev Returns the downcasted uint48 from uint256, reverting on * overflow (when the input is greater than largest uint48). * * Counterpart to Solidity's `uint48` operator. * * Requirements: * * - input must fit into 48 bits */ function toUint48(uint256 value) internal pure returns (uint48) { if (value > type(uint48).max) { revert SafeCastOverflowedUintDowncast(48, value); } return uint48(value); } /** * @dev Returns the downcasted uint40 from uint256, reverting on * overflow (when the input is greater than largest uint40). * * Counterpart to Solidity's `uint40` operator. * * Requirements: * * - input must fit into 40 bits */ function toUint40(uint256 value) internal pure returns (uint40) { if (value > type(uint40).max) { revert SafeCastOverflowedUintDowncast(40, value); } return uint40(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 value) internal pure returns (uint32) { if (value > type(uint32).max) { revert SafeCastOverflowedUintDowncast(32, value); } return uint32(value); } /** * @dev Returns the downcasted uint24 from uint256, reverting on * overflow (when the input is greater than largest uint24). * * Counterpart to Solidity's `uint24` operator. * * Requirements: * * - input must fit into 24 bits */ function toUint24(uint256 value) internal pure returns (uint24) { if (value > type(uint24).max) { revert SafeCastOverflowedUintDowncast(24, value); } return uint24(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits */ function toUint16(uint256 value) internal pure returns (uint16) { if (value > type(uint16).max) { revert SafeCastOverflowedUintDowncast(16, value); } return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits */ function toUint8(uint256 value) internal pure returns (uint8) { if (value > type(uint8).max) { revert SafeCastOverflowedUintDowncast(8, value); } return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. */ function toUint256(int256 value) internal pure returns (uint256) { if (value < 0) { revert SafeCastOverflowedIntToUint(value); } return uint256(value); } /** * @dev Returns the downcasted int248 from int256, reverting on * overflow (when the input is less than smallest int248 or * greater than largest int248). * * Counterpart to Solidity's `int248` operator. * * Requirements: * * - input must fit into 248 bits */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(248, value); } } /** * @dev Returns the downcasted int240 from int256, reverting on * overflow (when the input is less than smallest int240 or * greater than largest int240). * * Counterpart to Solidity's `int240` operator. * * Requirements: * * - input must fit into 240 bits */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(240, value); } } /** * @dev Returns the downcasted int232 from int256, reverting on * overflow (when the input is less than smallest int232 or * greater than largest int232). * * Counterpart to Solidity's `int232` operator. * * Requirements: * * - input must fit into 232 bits */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(232, value); } } /** * @dev Returns the downcasted int224 from int256, reverting on * overflow (when the input is less than smallest int224 or * greater than largest int224). * * Counterpart to Solidity's `int224` operator. * * Requirements: * * - input must fit into 224 bits */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(224, value); } } /** * @dev Returns the downcasted int216 from int256, reverting on * overflow (when the input is less than smallest int216 or * greater than largest int216). * * Counterpart to Solidity's `int216` operator. * * Requirements: * * - input must fit into 216 bits */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(216, value); } } /** * @dev Returns the downcasted int208 from int256, reverting on * overflow (when the input is less than smallest int208 or * greater than largest int208). * * Counterpart to Solidity's `int208` operator. * * Requirements: * * - input must fit into 208 bits */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(208, value); } } /** * @dev Returns the downcasted int200 from int256, reverting on * overflow (when the input is less than smallest int200 or * greater than largest int200). * * Counterpart to Solidity's `int200` operator. * * Requirements: * * - input must fit into 200 bits */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(200, value); } } /** * @dev Returns the downcasted int192 from int256, reverting on * overflow (when the input is less than smallest int192 or * greater than largest int192). * * Counterpart to Solidity's `int192` operator. * * Requirements: * * - input must fit into 192 bits */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(192, value); } } /** * @dev Returns the downcasted int184 from int256, reverting on * overflow (when the input is less than smallest int184 or * greater than largest int184). * * Counterpart to Solidity's `int184` operator. * * Requirements: * * - input must fit into 184 bits */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(184, value); } } /** * @dev Returns the downcasted int176 from int256, reverting on * overflow (when the input is less than smallest int176 or * greater than largest int176). * * Counterpart to Solidity's `int176` operator. * * Requirements: * * - input must fit into 176 bits */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(176, value); } } /** * @dev Returns the downcasted int168 from int256, reverting on * overflow (when the input is less than smallest int168 or * greater than largest int168). * * Counterpart to Solidity's `int168` operator. * * Requirements: * * - input must fit into 168 bits */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(168, value); } } /** * @dev Returns the downcasted int160 from int256, reverting on * overflow (when the input is less than smallest int160 or * greater than largest int160). * * Counterpart to Solidity's `int160` operator. * * Requirements: * * - input must fit into 160 bits */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(160, value); } } /** * @dev Returns the downcasted int152 from int256, reverting on * overflow (when the input is less than smallest int152 or * greater than largest int152). * * Counterpart to Solidity's `int152` operator. * * Requirements: * * - input must fit into 152 bits */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(152, value); } } /** * @dev Returns the downcasted int144 from int256, reverting on * overflow (when the input is less than smallest int144 or * greater than largest int144). * * Counterpart to Solidity's `int144` operator. * * Requirements: * * - input must fit into 144 bits */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(144, value); } } /** * @dev Returns the downcasted int136 from int256, reverting on * overflow (when the input is less than smallest int136 or * greater than largest int136). * * Counterpart to Solidity's `int136` operator. * * Requirements: * * - input must fit into 136 bits */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(136, value); } } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(128, value); } } /** * @dev Returns the downcasted int120 from int256, reverting on * overflow (when the input is less than smallest int120 or * greater than largest int120). * * Counterpart to Solidity's `int120` operator. * * Requirements: * * - input must fit into 120 bits */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(120, value); } } /** * @dev Returns the downcasted int112 from int256, reverting on * overflow (when the input is less than smallest int112 or * greater than largest int112). * * Counterpart to Solidity's `int112` operator. * * Requirements: * * - input must fit into 112 bits */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(112, value); } } /** * @dev Returns the downcasted int104 from int256, reverting on * overflow (when the input is less than smallest int104 or * greater than largest int104). * * Counterpart to Solidity's `int104` operator. * * Requirements: * * - input must fit into 104 bits */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(104, value); } } /** * @dev Returns the downcasted int96 from int256, reverting on * overflow (when the input is less than smallest int96 or * greater than largest int96). * * Counterpart to Solidity's `int96` operator. * * Requirements: * * - input must fit into 96 bits */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(96, value); } } /** * @dev Returns the downcasted int88 from int256, reverting on * overflow (when the input is less than smallest int88 or * greater than largest int88). * * Counterpart to Solidity's `int88` operator. * * Requirements: * * - input must fit into 88 bits */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(88, value); } } /** * @dev Returns the downcasted int80 from int256, reverting on * overflow (when the input is less than smallest int80 or * greater than largest int80). * * Counterpart to Solidity's `int80` operator. * * Requirements: * * - input must fit into 80 bits */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(80, value); } } /** * @dev Returns the downcasted int72 from int256, reverting on * overflow (when the input is less than smallest int72 or * greater than largest int72). * * Counterpart to Solidity's `int72` operator. * * Requirements: * * - input must fit into 72 bits */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(72, value); } } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(64, value); } } /** * @dev Returns the downcasted int56 from int256, reverting on * overflow (when the input is less than smallest int56 or * greater than largest int56). * * Counterpart to Solidity's `int56` operator. * * Requirements: * * - input must fit into 56 bits */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(56, value); } } /** * @dev Returns the downcasted int48 from int256, reverting on * overflow (when the input is less than smallest int48 or * greater than largest int48). * * Counterpart to Solidity's `int48` operator. * * Requirements: * * - input must fit into 48 bits */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(48, value); } } /** * @dev Returns the downcasted int40 from int256, reverting on * overflow (when the input is less than smallest int40 or * greater than largest int40). * * Counterpart to Solidity's `int40` operator. * * Requirements: * * - input must fit into 40 bits */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(40, value); } } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(32, value); } } /** * @dev Returns the downcasted int24 from int256, reverting on * overflow (when the input is less than smallest int24 or * greater than largest int24). * * Counterpart to Solidity's `int24` operator. * * Requirements: * * - input must fit into 24 bits */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(24, value); } } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(16, value); } } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(8, value); } } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive if (value > uint256(type(int256).max)) { revert SafeCastOverflowedUintToInt(value); } return int256(value); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library Constants { /// @notice Flooring protocol /// @dev floor token amount of 1 NFT (with 18 decimals) uint256 public constant FLOOR_TOKEN_AMOUNT = 1_000_000 ether; /// @dev The minimum vip level required to use `proxy collection` uint8 public constant PROXY_COLLECTION_VIP_THRESHOLD = 3; /// @notice Rolling Bucket Constant Conf uint256 public constant BUCKET_SPAN_1 = 259199 seconds; // BUCKET_SPAN minus 1, used for rounding up uint256 public constant BUCKET_SPAN = 3 days; uint256 public constant MAX_LOCKING_BUCKET = 240; uint256 public constant MAX_LOCKING_PERIOD = 720 days; // MAX LOCKING BUCKET * BUCKET_SPAN /// @notice Auction Config uint256 public constant FREE_AUCTION_PERIOD = 24 hours; uint256 public constant AUCTION_INITIAL_PERIODS = 24 hours; uint256 public constant AUCTION_COMPLETE_GRACE_PERIODS = 2 days; /// @dev admin fee charged per NFT when someone starts aution on expired safebox uint256 public constant AUCTION_ON_EXPIRED_SAFEBOX_COST = 0; /// @dev admin fee charged per NFT when owner starts aution on himself safebox uint256 public constant AUCTION_COST = 100 ether; /// @notice Raffle Config uint256 public constant RAFFLE_COST = 500 ether; uint256 public constant RAFFLE_COMPLETE_GRACE_PERIODS = 2 days; /// @notice Private offer Config uint256 public constant PRIVATE_OFFER_DURATION = 24 hours; uint256 public constant PRIVATE_OFFER_COMPLETE_GRACE_DURATION = 2 days; uint256 public constant PRIVATE_OFFER_COST = 0; uint256 public constant ADD_FREE_NFT_REWARD = 0; /// @notice Lock/Unlock config uint256 public constant USER_SAFEBOX_QUOTA_REFRESH_DURATION = 1 days; uint256 public constant USER_REDEMPTION_WAIVER_REFRESH_DURATION = 1 days; /// @notice The max percentage of the collection that one user can lock uint256 public constant USER_COLLECTION_LOCKED_BOUND_PCT = 50; /// @notice The max locking ratio of the collection that the NFTs in the vault can be redeemed uint256 public constant VAULT_REDEMPTION_MAX_LOKING_RATIO = 80; uint256 public constant VAULT_QUOTA_RESET_PERIOD = 3 days; /// @notice Activities Fee Rate /// @notice Fee rate used to distribute funds that collected from Auctions on expired safeboxes. /// these auction would be settled using credit token uint256 public constant FREE_AUCTION_FEE_RATE_BIPS = 2000; // 20% uint256 public constant VIP_LEVEL_COUNT = 8; struct AuctionBidOption { uint256 extendDurationSecs; uint256 minimumRaisePct; uint256 vipLevel; } function getVipLockingBuckets(uint256 vipLevel) internal pure returns (uint256 buckets) { require(vipLevel < VIP_LEVEL_COUNT); assembly { switch vipLevel case 1 { buckets := 1 } case 2 { buckets := 5 } case 3 { buckets := 20 } case 4 { buckets := 60 } case 5 { buckets := 120 } case 6 { buckets := 180 } case 7 { buckets := MAX_LOCKING_BUCKET } } } function getVipLevel(uint256 totalCredit) internal pure returns (uint8) { if (totalCredit < 30_000 ether) { return 0; } else if (totalCredit < 100_000 ether) { return 1; } else if (totalCredit < 300_000 ether) { return 2; } else if (totalCredit < 1_000_000 ether) { return 3; } else if (totalCredit < 3_000_000 ether) { return 4; } else if (totalCredit < 10_000_000 ether) { return 5; } else if (totalCredit < 30_000_000 ether) { return 6; } else { return 7; } } function getVipBalanceRequirements(uint256 vipLevel) internal pure returns (uint256 required) { require(vipLevel < VIP_LEVEL_COUNT); assembly { switch vipLevel case 1 { required := 30000 } case 2 { required := 100000 } case 3 { required := 300000 } case 4 { required := 1000000 } case 5 { required := 3000000 } case 6 { required := 10000000 } case 7 { required := 30000000 } } /// credit token should be scaled with 18 decimals(1 ether == 10**18) unchecked { return required * 1 ether; } } function getBidOption(uint256 idx) internal pure returns (AuctionBidOption memory) { require(idx < 4); AuctionBidOption[4] memory bidOptions = [ AuctionBidOption({extendDurationSecs: 5 minutes, minimumRaisePct: 1, vipLevel: 0}), AuctionBidOption({extendDurationSecs: 8 hours, minimumRaisePct: 10, vipLevel: 3}), AuctionBidOption({extendDurationSecs: 16 hours, minimumRaisePct: 20, vipLevel: 5}), AuctionBidOption({extendDurationSecs: 24 hours, minimumRaisePct: 40, vipLevel: 7}) ]; return bidOptions[idx]; } function raffleDurations(uint256 idx) internal pure returns (uint256 vipLevel, uint256 duration) { require(idx < 6); vipLevel = idx; assembly { switch idx case 1 { duration := 1 } case 2 { duration := 2 } case 3 { duration := 3 } case 4 { duration := 5 } case 5 { duration := 7 } } unchecked { duration *= 1 days; } } /// return locking ratio restrictions indicates that the vipLevel can utility infinite lock NFTs at corresponding ratio function getLockingRatioForInfinite(uint8 vipLevel) internal pure returns (uint256 ratio) { assembly { switch vipLevel case 1 { ratio := 0 } case 2 { ratio := 0 } case 3 { ratio := 20 } case 4 { ratio := 30 } case 5 { ratio := 40 } case 6 { ratio := 50 } case 7 { ratio := 80 } } } /// return locking ratio restrictions indicates that the vipLevel can utility safebox to lock NFTs at corresponding ratio function getLockingRatioForSafebox(uint8 vipLevel) internal pure returns (uint256 ratio) { assembly { switch vipLevel case 1 { ratio := 10 } case 2 { ratio := 20 } case 3 { ratio := 30 } case 4 { ratio := 40 } case 5 { ratio := 50 } case 6 { ratio := 60 } case 7 { ratio := 70 } } } function getRequiredStakingWithSelfRatio(uint256 requiredStaking, uint256 selfRatio) internal pure returns (uint256) { if (selfRatio < 10) { return requiredStaking; } return (selfRatio + 1) * requiredStaking / 10; } function getVipRequiredStakingWithDiscount(uint256 requiredStaking, uint8 vipLevel) internal pure returns (uint256) { if (vipLevel < 3) { return requiredStaking; } unchecked { /// the higher vip level, more discount for staking /// discount range: 5% - 25% return requiredStaking * (100 - (vipLevel - 2) * 5) / 100; } } function getRequiredStakingForLockRatio(uint256 locked, uint256 totalManaged) internal pure returns (uint256) { if (totalManaged <= 0) { return 1200 ether; } unchecked { uint256 lockingRatioPct = locked * 100 / totalManaged; if (lockingRatioPct <= 40) { return 1200 ether; } else if (lockingRatioPct < 60) { return 1320 ether + ((lockingRatioPct - 40) >> 1) * 120 ether; } else if (lockingRatioPct < 70) { return 2640 ether + ((lockingRatioPct - 60) >> 1) * 240 ether; } else if (lockingRatioPct < 80) { return 4080 ether + ((lockingRatioPct - 70) >> 1) * 480 ether; } else if (lockingRatioPct < 90) { return 6960 ether + ((lockingRatioPct - 80) >> 1) * 960 ether; } else if (lockingRatioPct < 100) { /// 108000 * 2^x return (108000 ether << ((lockingRatioPct - 90) >> 1)) / 5; } else { return 345600 ether; } } } function getVaultAuctionDurationAtLR(uint256 lockingRatio) internal pure returns (uint256) { if (lockingRatio < 80) return 1 hours; else if (lockingRatio < 85) return 3 hours; else if (lockingRatio < 90) return 6 hours; else if (lockingRatio < 95) return 12 hours; else return 24 hours; } function getSafeboxPeriodQuota(uint8 vipLevel) internal pure returns (uint16 quota) { assembly { switch vipLevel case 0 { quota := 0 } case 1 { quota := 1 } case 2 { quota := 2 } case 3 { quota := 4 } case 4 { quota := 8 } case 5 { quota := 16 } case 6 { quota := 32 } case 7 { quota := 64 } } } function getSafeboxUserQuota(uint8 vipLevel) internal pure returns (uint16 quota) { assembly { switch vipLevel case 0 { quota := 0 } case 1 { quota := 4 } case 2 { quota := 8 } case 3 { quota := 16 } case 4 { quota := 32 } case 5 { quota := 64 } case 6 { quota := 128 } case 7 { quota := 256 } } } function getVaultContQuotaAtLR(uint256 lockingRatio) internal pure returns (uint32 contQuota) { if (lockingRatio <= 70) { return 1; } else if (lockingRatio <= 80) { return 2; } else if (lockingRatio <= 90) { return 4; } else { return 8; } } /// two options to redeem from vault /// pay fee with fragment token or consume quota function getVaultFeeAtLR(uint256 lockingRatio) internal pure returns (uint256 fee, uint32 quota) { if (lockingRatio <= 50) { return (20000 ether, 1); } else if (lockingRatio <= 60) { return (40000 ether, 2); } else if (lockingRatio <= 70) { return (60000 ether, 4); } else if (lockingRatio <= 80) { return (80000 ether, 8); } else { /// note. above 80, can not redeem from the vault return (100000 ether, 10); } } /// @return protocol fee after discount function getListingProtocolFeeWithDiscount(uint256 protocolFee, uint8 vipLevel) internal pure returns (uint256) { if (vipLevel < 3) { return protocolFee; } unchecked { /// the higher vip level, more discount for protocol fee /// discount range: 5% - 25% return protocolFee * (100 - (vipLevel - 2) * 5) / 100; } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library Errors { /// @notice Safe Box error error SafeBoxHasExpire(); error SafeBoxNotExist(); error SafeBoxHasNotExpire(); error SafeBoxAlreadyExist(); error NoMatchingSafeBoxKey(); error SafeBoxKeyAlreadyExist(); /// @notice Auction error error AuctionHasNotCompleted(); error AuctionHasExpire(); error AuctionBidIsNotHighEnough(); error AuctionBidTokenMismatch(); error AuctionSelfBid(); error AuctionInvalidBidAmount(); error AuctionNotExist(); error SafeBoxAuctionWindowHasPassed(); /// @notice Activity common error error NftHasActiveActivities(); error ActivityHasNotCompleted(); error ActivityHasExpired(); error ActivityNotExist(); /// @notice User account error error InsufficientCredit(); error InsufficientBalanceForVipLevel(); error NoPrivilege(); /// @notice Parameter error error InvalidParam(); error NftCollectionNotSupported(); error NftCollectionAlreadySupported(); error ClaimableNftInsufficient(); error TokenNotSupported(); error PeriodQuotaExhausted(); error UserQuotaExhausted(); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "./IMulticall.sol"; interface IFlooring is IERC721Receiver, IMulticall { /// Admin Operations /// @notice Add new collection for Flooring Protocol function supportNewCollection(address _originalNFT, address fragmentToken) external payable; /// @notice Add new token which will be used as settlement token in Flooring Protocol /// @param addOrRemove `true` means add token, `false` means remove token function supportNewToken(address _tokenAddress, bool addOrRemove) external payable; /// @notice set proxy collection config /// Note. the `tokenId`s of the proxy collection and underlying collection must be correspond one by one /// eg. Paraspace Derivative Token BAYC(nBAYC) -> BAYC function setCollectionProxy(address proxyCollection, address underlyingCollection) external payable; /// @notice withdraw platform fee accumulated. /// Note. withdraw from `address(this)`'s account. function withdrawPlatformFee(address token, uint256 amount) external payable; /// @notice Deposit and lock credit token on behalf of receiver /// user can not withdraw these tokens until `unlockCredit` is called. function addAndLockCredit(address receiver, uint256 amount) external payable; /// @notice Unlock user credit token to allow withdraw /// used to release investors' funds as time goes /// Note. locked credit can be used to operate safeboxes(lock/unlock...) function unlockCredit(address receiver, uint256 amount) external payable; /// User Operations /// @notice User deposits token to the Floor Contract /// @param onBehalfOf deposit token into `onBehalfOf`'s account.(note. the tokens of msg.sender will be transfered) function addTokens(address onBehalfOf, address token, uint256 amount) external payable; /// @notice User removes token from Floor Contract /// @param receiver who will receive the funds.(note. the token of msg.sender will be transfered) function removeTokens(address token, uint256 amount, address receiver) external; /// @notice Lock specified `nftIds` into Flooring Safeboxes and receive corresponding Fragment Tokens of the `collection` /// @param expiryTs when the safeboxes expired, `0` means infinite lock without expiry /// @param vipLevel vip tier required in this lock operation /// @param maxCredit maximum credit can be locked in this operation, if real cost exceeds this limit, the tx will fail /// @param onBehalfOf who will receive the safebox and fragment tokens.(note. the NFTs of the msg.sender will be transfered) function lockNFTs( address collection, uint256[] memory nftIds, uint256 expiryTs, uint256 vipLevel, uint256 maxCredit, address onBehalfOf ) external returns (uint256); /// @notice Extend the exist safeboxes with longer lock duration with more credit token staked /// @param expiryTs new expiry timestamp, should bigger than previous expiry function extendKeys( address collection, uint256[] memory nftIds, uint256 expiryTs, uint256 vipLevel, uint256 maxCredit ) external returns (uint256); /// @notice Unlock specified `nftIds` which had been locked previously /// sender's wallet should have enough Fragment Tokens of the `collection` which will be burned to redeem the NFTs /// @param expiryTs the latest nft's expiry, we need this to clear locking records /// if the value smaller than the latest nft's expiry, the tx will fail /// if part of `nftIds` were locked infinitely, just skip these expiry /// @param receiver who will receive the NFTs. /// note. - The safeboxes of the msg.sender will be removed. /// - The Fragment Tokens of the msg.sender will be burned. function unlockNFTs(address collection, uint256 expiryTs, uint256[] memory nftIds, address receiver) external; /// @notice Fragment specified `nftIds` into Floor Vault and receive Fragment Tokens without any locking /// after fragmented, any one has enough Fragment Tokens can redeem there `nftIds` /// @param onBehalfOf who will receive the fragment tokens and the vault contribution quota.(note. the NFTs of the msg.sender will be transfered) /// if onBehalfOf == address(this), it means msg.sender intends to swap the same quantity of NFTs from the vault as the `nftIds` function fragmentNFTs(address collection, uint256[] memory nftIds, address onBehalfOf) external; /// @notice Kick expired safeboxes to the vault function tidyExpiredNFTs(address collection, uint256[] memory nftIds) external; /// @notice Randomly claim `claimCnt` NFTs from Floor Vault /// sender's wallet should have enough Fragment Tokens of the `collection` which will be burned to redeem the NFTs /// @param maxCredit maximum credit can be costed in this operation, if real cost exceeds this limit, the tx will fail /// @param receiver who will receive the NFTs. /// note. - the msg.sender will pay the redemption cost. /// - The Fragment Tokens of the msg.sender will be burned. function claimRandomNFT(address collection, uint256 claimCnt, uint256 maxCredit, address receiver) external returns (uint256); /// @notice Start auctions on specified `nftIds` with an initial bid price(`bidAmount`) /// This kind of auctions will be settled with Floor Credit Token /// @param bidAmount initial bid price function initAuctionOnExpiredSafeBoxes( address collection, uint256[] memory nftIds, address bidToken, uint256 bidAmount ) external; /// @notice Start auctions on specified `nftIds` index in the vault with an initial bid price(`bidAmount`) /// This kind of auctions will be settled with Fragment Token of the collection /// @param bidAmount initial bid price function initAuctionOnVault(address collection, uint256[] memory vaultIdx, address bidToken, uint96 bidAmount) external; /// @notice Owner starts auctions on his locked Safeboxes /// @param maxExpiry the latest nft's expiry, we need this to clear locking records /// @param token which token should be used to settle auctions(bid, settle) /// @param minimumBid minimum bid price when someone place a bid on the auction function ownerInitAuctions( address collection, uint256[] memory nftIds, uint256 maxExpiry, address token, uint256 minimumBid ) external; /// @notice Place a bid on specified `nftId`'s action /// @param bidAmount bid price /// @param bidOptionIdx which option used to extend auction expiry and bid price function placeBidOnAuction(address collection, uint256 nftId, uint256 bidAmount, uint256 bidOptionIdx) external payable; /// @notice Settle auctions of `nftIds` function settleAuctions(address collection, uint256[] memory nftIds) external; struct RaffleInitParam { address collection; uint256[] nftIds; /// @notice which token used to buy and settle raffle address ticketToken; /// @notice price per ticket uint96 ticketPrice; /// @notice max tickets amount can be sold uint32 maxTickets; /// @notice durationIdx used to get how long does raffles last uint256 duration; /// @notice the largest epxiry of nfts, we need this to clear locking records uint256 maxExpiry; } /// @notice Owner start raffles on locked `nftIds` function ownerInitRaffles(RaffleInitParam memory param) external; /// @notice Buy `nftId`'s raffle tickets /// @param ticketCnt how many tickets should be bought in this operation function buyRaffleTickets(address collectionId, uint256 nftId, uint256 ticketCnt) external payable; /// @notice Settle raffles of `nftIds` function settleRaffles(address collectionId, uint256[] memory nftIds) external; struct PrivateOfferInitParam { address collection; uint256[] nftIds; /// @notice the largest epxiry of nfts, we need this to clear locking records uint256 maxExpiry; /// @notice who will receive the otc offers address receiver; /// @notice which token used to settle offers address token; /// @notice price of the offers uint96 price; } /// @notice Owner start private offers(otc) on locked `nftIds` function ownerInitPrivateOffers(PrivateOfferInitParam memory param) external; enum OfferOpType { Cancel, Decline, ChangePrice } struct ChangeOfferPriceData { uint96[] priceList; } /// @notice Owner or Receiver cancel the private offers of `nftIds` function modifyOffers(address collectionId, uint256[] memory nftIds, OfferOpType opTy, bytes calldata data) external; /// @notice Receiver accept the private offers of `nftIds` function buyerAcceptPrivateOffers(address collectionId, uint256[] memory nftIds, uint256 maxSafeboxExpiry) external payable; /// @notice Clear expired or mismatching safeboxes of `nftIds` in user account /// @param onBehalfOf whose account will be recalculated /// @return credit amount has been released function removeExpiredKeyAndRestoreCredit( address collection, uint256[] memory nftIds, address onBehalfOf, bool verifyLocking ) external returns (uint256); /// @notice Update user's staking credit status by iterating all active collections in user account /// @param onBehalfOf whose account will be recalculated /// @return availableCredit how many credit available to use after this opeartion function recalculateAvailableCredit(address onBehalfOf) external returns (uint256 availableCredit); /// Util operations /// @notice Called by external contracts to access granular pool state /// @param slot Key of slot to sload /// @return value The value of the slot as bytes32 function extsload(bytes32 slot) external view returns (bytes32 value); /// @notice Called by external contracts to access granular pool state /// @param slot Key of slot to start sloading from /// @param nSlots Number of slots to load into return value /// @return value The value of the sload-ed slots concatenated as dynamic bytes function extsload(bytes32 slot, uint256 nSlots) external view returns (bytes memory value); function creditToken() external view returns (address); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; interface IFragmentToken { error CallerIsNotTrustedContract(); function mint(address account, uint256 amount) external; function burn(address account, uint256 amount) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; /// @title Multicall interface /// @notice Enables calling multiple methods in a single call to the contract interface IMulticall { /** * @dev A call to an address target failed. The target may have reverted. */ error FailedMulticall(); struct CallData { address target; bytes callData; } /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed /// @param data The encoded function data for each of the calls to make to this contract /// @return results The results from each of the calls passed in via data function multicall(bytes[] calldata data) external returns (bytes[] memory results); /// @notice Allow trusted caller to call specified addresses through the Contract /// @dev The `msg.value` should not be trusted for any method callable from multicall. /// @param calls The encoded function data and target for each of the calls to make to this contract /// @return results The results from each of the calls passed in via calls function extMulticall(CallData[] calldata calls) external returns (bytes[] memory); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library Array { /// @notice Compress `data` to [Length]:{[BytesLength][val...]} /// eg. [0, 255, 256] will be convert to bytes series: 0x03 0x00 0x01 0xFF 0x02 0x00 0x01 /// 0x03 means there are 3 numbers /// 0x00 means first number is 0 /// 0x01 means next number(255) has 1 byte to store the real value /// 0xFF equals 255 /// 256 need 2 bytes(0x02) to store, and its value represented in hex is 0x0100 function encodeUints(uint256[] memory data) internal pure returns (bytes memory res) { uint256 dataLen = data.length; require(dataLen <= type(uint8).max); unchecked { uint256 totalBytes; for (uint256 i; i < dataLen; ++i) { uint256 val = data[i]; while (val > 0) { val >>= 8; ++totalBytes; } } res = new bytes(dataLen + totalBytes + 1); assembly { /// skip res's length, store data length mstore8(add(res, 0x20), dataLen) } /// start from the second element idx uint256 resIdx = 0x21; for (uint256 i; i < dataLen; ++i) { uint256 val = data[i]; uint256 byteLen; while (val > 0) { val >>= 8; ++byteLen; } assembly { /// store bytes length of the `i`th element mstore8(add(res, resIdx), byteLen) } ++resIdx; val = data[i]; for (uint256 j; j < byteLen; ++j) { assembly { mstore8(add(res, resIdx), val) } val >>= 8; ++resIdx; } } } } function decodeUints(bytes memory data) internal pure returns (uint256[] memory res) { uint256 dataLen = data.length; require(dataLen > 0); res = new uint256[](uint8(data[0])); uint256 k; unchecked { for (uint256 i = 1; i < dataLen; ++i) { uint256 byteLen = uint8(data[i]); /// if byteLen is zero, it means current element is zero, no need to update `res`, just increment `k` if (byteLen > 0) { uint256 tmp; /// combine next `byteLen` bytes to `tmp` for (uint256 j; j < byteLen; ++j) { /// skip `byteLen` ++i; tmp |= ((uint256(uint8(data[i]))) << (j * 8)); } res[k] = tmp; } ++k; } } } function slice(uint256[] memory data, uint256 start, uint256 length) internal pure returns (uint256[] memory res) { require(start + length <= data.length); res = new uint256[](length); unchecked { start *= 0x20; length *= 0x20; } for (uint256 i = 0x20; i <= length;) { assembly { mstore(add(res, i), mload(add(data, add(i, start)))) } unchecked { i += 0x20; } } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library CurrencyTransfer { /// @notice Thrown when an ERC20 transfer fails error ERC20TransferFailed(); /// @notice Thrown when an NATIVE transfer fails error NativeTransferFailed(); address public constant NATIVE = address(0); function safeTransfer(address token, address to, uint256 amount) internal { // ref // https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html // implementation from // https://github.com/transmissions11/solmate/blob/v7/src/utils/SafeTransferLib.sol // https://github.com/Uniswap/v4-core/blob/main/contracts/types/Currency.sol bool success; if (token == NATIVE) { assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } if (!success) revert NativeTransferFailed(); } else { /// @solidity memory-safe-assembly assembly { // We'll write our calldata to this slot below, but restore it later. let memPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(0, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(4, to) // Append the "to" argument. mstore(36, amount) // Append the "amount" argument. 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 that's the total length of our calldata (4 + 32 * 2) // Counterintuitively, this call() must be positioned after the or() in the // surrounding and() because and() evaluates its arguments from right to left. call(gas(), token, 0, 0, 68, 0, 32) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, memPointer) // Restore the memPointer. } if (!success) revert ERC20TransferFailed(); } } function safeTransferFrom(address token, address from, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let memPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(4, from) // Append and mask the "from" argument. mstore(36, to) // Append and mask the "to" argument. // Append the "amount" argument. Masking not required as it's a full 32 byte type. mstore(68, amount) 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, 0, 100, 0, 32) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, memPointer) // Restore the memPointer. } if (!success) revert ERC20TransferFailed(); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; library ERC721Transfer { /// @notice Thrown when an ERC721 transfer fails error ERC721TransferFailed(); function safeTransferFrom(address collection, address from, address to, uint256 tokenId) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let memPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(0, 0x42842e0e00000000000000000000000000000000000000000000000000000000) mstore(4, from) // Append and mask the "from" argument. mstore(36, to) // Append and mask the "to" argument. // Append the "tokenId" argument. Masking not required as it's a full 32 byte type. mstore(68, tokenId) 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(), collection, 0, 0, 100, 0, 32) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, memPointer) // Restore the memPointer. } if (!success) revert ERC721TransferFailed(); } function safeBatchTransferFrom(address collection, address from, address to, uint256[] memory tokenIds) internal { unchecked { uint256 len = tokenIds.length; for (uint256 i; i < len; ++i) { safeTransferFrom(collection, from, to, tokenIds[i]); } } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "../Constants.sol"; library RollingBuckets { error BucketValueExceedsLimit(); error BucketLengthExceedsLimit(); /// @dev `MAX_BUCKET_SIZE` must be a multiple of `WORD_ELEMENT_SIZE`, /// otherwise some words may be incomplete which may lead to incorrect bit positioning. uint256 constant MAX_BUCKET_SIZE = Constants.MAX_LOCKING_BUCKET; /// @dev each `ELEMENT_BIT_SIZE` bits stores an element uint256 constant ELEMENT_BIT_SIZE = 24; /// @dev `ELEMENT_BIT_SIZE` bits mask uint256 constant MASK = 0xFFFFFF; /// @dev one word(256 bits) can store (256 // ELEMENT_BIT_SIZE) elements uint256 constant WORD_ELEMENT_SIZE = 10; function position(uint256 tick) private pure returns (uint256 wordPos, uint256 bitPos) { unchecked { wordPos = tick / WORD_ELEMENT_SIZE; bitPos = tick % WORD_ELEMENT_SIZE; } } function get(mapping(uint256 => uint256) storage buckets, uint256 bucketStamp) internal view returns (uint256) { unchecked { (uint256 wordPos, uint256 bitPos) = position(bucketStamp % MAX_BUCKET_SIZE); return (buckets[wordPos] >> (bitPos * ELEMENT_BIT_SIZE)) & MASK; } } /// [first, last) function batchGet(mapping(uint256 => uint256) storage buckets, uint256 firstStamp, uint256 lastStamp) internal view returns (uint256[] memory) { if (firstStamp > lastStamp) revert BucketLengthExceedsLimit(); uint256 len; unchecked { len = lastStamp - firstStamp; } if (len > MAX_BUCKET_SIZE) { revert BucketLengthExceedsLimit(); } uint256[] memory result = new uint256[](len); uint256 resultIndex; unchecked { (uint256 wordPos, uint256 bitPos) = position(firstStamp % MAX_BUCKET_SIZE); uint256 wordVal = buckets[wordPos]; uint256 mask = MASK << (bitPos * ELEMENT_BIT_SIZE); for (uint256 i = firstStamp; i < lastStamp;) { assembly { /// increase idx firstly to skip `array length` resultIndex := add(resultIndex, 0x20) /// wordVal store order starts from lowest bit /// result[i] = ((wordVal & mask) >> (bitPos * ELEMENT_BIT_SIZE)) mstore(add(result, resultIndex), shr(mul(bitPos, ELEMENT_BIT_SIZE), and(wordVal, mask))) mask := shl(ELEMENT_BIT_SIZE, mask) bitPos := add(bitPos, 1) i := add(i, 1) } if (bitPos == WORD_ELEMENT_SIZE) { (wordPos, bitPos) = position(i % MAX_BUCKET_SIZE); wordVal = buckets[wordPos]; mask = MASK; } } } return result; } function set(mapping(uint256 => uint256) storage buckets, uint256 bucketStamp, uint256 value) internal { if (value > MASK) revert BucketValueExceedsLimit(); unchecked { (uint256 wordPos, uint256 bitPos) = position(bucketStamp % MAX_BUCKET_SIZE); uint256 wordValue = buckets[wordPos]; uint256 newValue = value << (bitPos * ELEMENT_BIT_SIZE); uint256 newWord = (wordValue & ~(MASK << (bitPos * ELEMENT_BIT_SIZE))) | newValue; buckets[wordPos] = newWord; } } function batchSet(mapping(uint256 => uint256) storage buckets, uint256 firstStamp, uint256[] memory values) internal { uint256 valLength = values.length; if (valLength > MAX_BUCKET_SIZE) revert BucketLengthExceedsLimit(); if (firstStamp > (type(uint256).max - valLength)) { revert BucketLengthExceedsLimit(); } unchecked { (uint256 wordPos, uint256 bitPos) = position(firstStamp % MAX_BUCKET_SIZE); uint256 wordValue = buckets[wordPos]; uint256 mask = ~(MASK << (bitPos * ELEMENT_BIT_SIZE)); /// reuse val length as End Postion valLength = (valLength + 1) * 0x20; /// start from first element offset for (uint256 i = 0x20; i < valLength; i += 0x20) { uint256 val; assembly { val := mload(add(values, i)) } if (val > MASK) revert BucketValueExceedsLimit(); assembly { /// newVal = val << (bitPos * BIT_SIZE) let newVal := shl(mul(bitPos, ELEMENT_BIT_SIZE), val) /// save newVal to wordVal, clear corresponding bits and set them as newVal /// wordValue = (wordVal & mask) | newVal wordValue := or(and(wordValue, mask), newVal) /// goto next number idx in current word bitPos := add(bitPos, 1) /// mask = ~(MASK << (bitPos, BIT_SIZE)) mask := not(shl(mul(bitPos, ELEMENT_BIT_SIZE), MASK)) } if (bitPos == WORD_ELEMENT_SIZE) { /// store hole word buckets[wordPos] = wordValue; /// get next word' position (wordPos, bitPos) = position((firstStamp + (i / 0x20)) % MAX_BUCKET_SIZE); wordValue = buckets[wordPos]; /// restore mask to make it start from lowest bits mask = ~MASK; } } /// store last word which may incomplete buckets[wordPos] = wordValue; } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../library/RollingBuckets.sol"; import "../library/ERC721Transfer.sol"; import "../library/Array.sol"; import "../Errors.sol"; import "../Constants.sol"; import "./User.sol"; import "./Helper.sol"; import "./Structs.sol"; import {SafeBoxLib} from "./SafeBox.sol"; import "../interface/IFlooring.sol"; library CollectionLib { using SafeBoxLib for SafeBox; using SafeCast for uint256; using RollingBuckets for mapping(uint256 => uint256); using UserLib for CollectionAccount; using UserLib for UserFloorAccount; event LockNft( address indexed sender, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys, uint256 safeBoxExpiryTs, uint256 minMaintCredit, address proxyCollection ); event ExtendKey( address indexed operator, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys, uint256 safeBoxExpiryTs, uint256 minMaintCredit ); event UnlockNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, address proxyCollection ); event RemoveExpiredKey( address indexed operator, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds, uint256[] safeBoxKeys ); event ExpiredNftToVault(address indexed operator, address indexed collection, uint256[] tokenIds); event FragmentNft( address indexed operator, address indexed onBehalfOf, address indexed collection, uint256[] tokenIds ); event ClaimRandomNft( address indexed operator, address indexed receiver, address indexed collection, uint256[] tokenIds, uint256 creditCost ); function fragmentNFTs( CollectionState storage collectionState, UserFloorAccount storage account, address collection, uint256[] memory nftIds, address onBehalfOf ) public { uint256 nftLen = nftIds.length; /// tricky logic: if `onBehalfOf` is the contract, shaffle the correspounding amount of NFTs bool shuffle = onBehalfOf == address(this); uint256[] memory pickedTokenIds; if (shuffle) { uint256 vaultCnt = collectionState.freeTokenIds.length; /// no enough nft to shuffle if (nftLen > vaultCnt) revert Errors.ClaimableNftInsufficient(); (pickedTokenIds,) = pickRandomFromVault( collectionState, vaultCnt, vaultCnt + collectionState.activeSafeBoxCnt, nftLen, true ); } /// after shuffling, supply new NFTs to vault uint32 contQuota; for (uint256 i; i < nftLen;) { collectionState.freeTokenIds.push(nftIds[i]); if (!shuffle) { contQuota += Constants.getVaultContQuotaAtLR(Helper.calculateLockingRatio(collectionState, 0)); } unchecked { ++i; } } if (!shuffle) { /// if no shuffling, give back the Fragment Tokens collectionState.floorToken.mint(onBehalfOf, Constants.FLOOR_TOKEN_AMOUNT * nftLen); Helper.checkAndUpdateVaultQuota(account.getByKey(collection), int32(contQuota)); ERC721Transfer.safeBatchTransferFrom(collection, msg.sender, address(this), nftIds); } else { /// if shuffling, transfer user's NFTs first to avoid repetition. ERC721Transfer.safeBatchTransferFrom(collection, msg.sender, address(this), nftIds); /// if shuffling, give back the picked NFTs ERC721Transfer.safeBatchTransferFrom(collection, address(this), msg.sender, pickedTokenIds); /// tracking the out NFTs emit ClaimRandomNft(msg.sender, msg.sender, collection, pickedTokenIds, 0); } emit FragmentNft(msg.sender, onBehalfOf, collection, nftIds); } struct LockInfo { bool isInfinite; uint256 currentBucket; uint256 newExpiryBucket; uint256 selfLocked; uint256 totalManaged; uint256 newRequireLockCredit; uint64 infiniteCnt; } function lockNfts( CollectionState storage collection, UserFloorAccount storage account, LockParam memory param, address onBehalfOf ) public returns (uint256 totalCreditCost) { if (onBehalfOf == address(this)) revert Errors.InvalidParam(); /// proxy collection only enabled when infinity lock if (param.collection != param.proxyCollection && param.expiryTs != 0) revert Errors.InvalidParam(); uint256 totalCredit = account.ensureVipCredit(param.vipLevel, param.creditToken); Helper.ensureMaxLocking(collection, param.vipLevel, param.expiryTs, param.nftIds.length, false); { uint8 maxVipLevel = Constants.getVipLevel(totalCredit); uint256 newLocked = param.nftIds.length; Helper.ensureProxyVipLevel(maxVipLevel, param.collection != param.proxyCollection); /// check period quota and global quota for the account Helper.checkAndUpdateUserSafeboxQuota(account, maxVipLevel, newLocked.toUint16()); /// don't try to add the collection account Helper.checkCollectionSafeboxQuota(account.getByKey(param.collection), collection, newLocked); } /// cache value to avoid multi-reads uint256 minMaintCredit = account.minMaintCredit; uint256[] memory nftIds = param.nftIds; uint256[] memory newKeys; { CollectionAccount storage userCollectionAccount = account.getOrAddCollection(param.collection); (totalCreditCost, newKeys) = _lockNfts(collection, userCollectionAccount, nftIds, param.expiryTs, param.vipLevel); // compute max credit for locking cost uint96 totalLockingCredit = userCollectionAccount.totalLockingCredit; { uint256 creditBuffer; unchecked { creditBuffer = totalCredit - totalLockingCredit; } if (totalCreditCost > creditBuffer || totalCreditCost > param.maxCreditCost) { revert Errors.InsufficientCredit(); } } totalLockingCredit += totalCreditCost.toUint96(); userCollectionAccount.totalLockingCredit = totalLockingCredit; if (totalLockingCredit > minMaintCredit) { account.minMaintCredit = totalLockingCredit; minMaintCredit = totalLockingCredit; } } account.updateVipKeyCount(param.vipLevel, int256(nftIds.length)); /// mint for `onBehalfOf`, transfer from msg.sender collection.floorToken.mint(onBehalfOf, Constants.FLOOR_TOKEN_AMOUNT * nftIds.length); ERC721Transfer.safeBatchTransferFrom(param.proxyCollection, msg.sender, address(this), nftIds); emit LockNft( msg.sender, onBehalfOf, param.collection, nftIds, newKeys, param.expiryTs, minMaintCredit, param.proxyCollection ); } function _lockNfts( CollectionState storage collectionState, CollectionAccount storage account, uint256[] memory nftIds, uint256 expiryTs, // treat 0 as infinite lock. uint8 vipLevel ) private returns (uint256, uint256[] memory) { LockInfo memory info = LockInfo({ isInfinite: expiryTs == 0, currentBucket: Helper.counterStamp(block.timestamp), newExpiryBucket: Helper.counterStamp(expiryTs), selfLocked: account.keyCnt, totalManaged: collectionState.activeSafeBoxCnt + collectionState.freeTokenIds.length, newRequireLockCredit: 0, infiniteCnt: collectionState.infiniteCnt }); if (info.isInfinite) { /// if it is infinite lock, we need load all buckets to calculate the staking cost info.newExpiryBucket = Helper.counterStamp(block.timestamp + Constants.MAX_LOCKING_PERIOD); } uint256[] memory buckets = Helper.prepareBucketUpdate(collectionState, info.currentBucket, info.newExpiryBucket); /// @dev `keys` used to log info, we just compact its fields into one 256 bits number uint256[] memory keys = new uint256[](nftIds.length); for (uint256 idx; idx < nftIds.length;) { uint256 lockedCredit = updateCountersAndGetSafeboxCredit(buckets, info, vipLevel); if (info.isInfinite) ++info.infiniteCnt; SafeBoxKey memory key = SafeBoxKey({ keyId: Helper.generateNextKeyId(collectionState), lockingCredit: lockedCredit.toUint96(), vipLevel: vipLevel }); account.addSafeboxKey(nftIds[idx], key); addSafeBox( collectionState, nftIds[idx], SafeBox({keyId: key.keyId, expiryTs: uint32(expiryTs), owner: msg.sender}) ); keys[idx] = SafeBoxLib.encodeSafeBoxKey(key); info.newRequireLockCredit += lockedCredit; unchecked { ++info.totalManaged; ++info.selfLocked; ++idx; } } if (info.isInfinite) { collectionState.infiniteCnt = info.infiniteCnt; } else { collectionState.countingBuckets.batchSet(info.currentBucket, buckets); if (info.newExpiryBucket > collectionState.lastUpdatedBucket) { collectionState.lastUpdatedBucket = uint64(info.newExpiryBucket); } } return (info.newRequireLockCredit, keys); } function unlockNfts( CollectionState storage collection, UserFloorAccount storage userAccount, address proxyCollection, address collectionId, uint256[] memory nftIds, uint256 maxExpiryTs, address receiver ) public { CollectionAccount storage userCollectionAccount = userAccount.getByKey(collectionId); SafeBoxKey[] memory releasedKeys = _unlockNfts(collection, maxExpiryTs, nftIds, userCollectionAccount); for (uint256 i = 0; i < releasedKeys.length;) { userAccount.updateVipKeyCount(releasedKeys[i].vipLevel, -1); unchecked { ++i; } } /// @dev if the receiver is the contract self, then unlock the safeboxes and dump the NFTs to the vault if (receiver == address(this)) { uint256 nftLen = nftIds.length; for (uint256 i; i < nftLen;) { collection.freeTokenIds.push(nftIds[i]); unchecked { ++i; } } emit FragmentNft(msg.sender, msg.sender, collectionId, nftIds); } else { collection.floorToken.burn(msg.sender, Constants.FLOOR_TOKEN_AMOUNT * nftIds.length); ERC721Transfer.safeBatchTransferFrom(proxyCollection, address(this), receiver, nftIds); } emit UnlockNft(msg.sender, receiver, collectionId, nftIds, proxyCollection); } function _unlockNfts( CollectionState storage collectionState, uint256 maxExpiryTs, uint256[] memory nftIds, CollectionAccount storage userCollectionAccount ) private returns (SafeBoxKey[] memory) { if (maxExpiryTs > 0 && maxExpiryTs < block.timestamp) revert Errors.SafeBoxHasExpire(); SafeBoxKey[] memory expiredKeys = new SafeBoxKey[](nftIds.length); uint256 currentBucketTime = Helper.counterStamp(block.timestamp); uint256 creditToRelease = 0; uint256[] memory buckets; /// if maxExpiryTs == 0, it means all nftIds in this batch being locked infinitely that we don't need to update countingBuckets if (maxExpiryTs > 0) { uint256 maxExpiryBucketTime = Math.min(Helper.counterStamp(maxExpiryTs), collectionState.lastUpdatedBucket); buckets = collectionState.countingBuckets.batchGet(currentBucketTime, maxExpiryBucketTime); } for (uint256 i; i < nftIds.length;) { uint256 nftId = nftIds[i]; if (Helper.hasActiveActivities(collectionState, nftId)) revert Errors.NftHasActiveActivities(); (SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) = Helper.useSafeBoxAndKey(collectionState, userCollectionAccount, nftId); creditToRelease += safeBoxKey.lockingCredit; if (safeBox.isInfiniteSafeBox()) { --collectionState.infiniteCnt; } else { uint256 limit = Helper.counterStamp(safeBox.expiryTs) - currentBucketTime; if (limit > buckets.length) revert(); for (uint256 idx; idx < limit;) { --buckets[idx]; unchecked { ++idx; } } } expiredKeys[i] = safeBoxKey; removeSafeBox(collectionState, nftId); userCollectionAccount.removeSafeboxKey(nftId); unchecked { ++i; } } userCollectionAccount.totalLockingCredit -= creditToRelease.toUint96(); if (buckets.length > 0) { collectionState.countingBuckets.batchSet(currentBucketTime, buckets); } return expiredKeys; } function extendLockingForKeys( CollectionState storage collection, UserFloorAccount storage userAccount, LockParam memory param ) public returns (uint256 totalCreditCost) { uint8 newVipLevel = uint8(param.vipLevel); uint256 totalCredit = userAccount.ensureVipCredit(newVipLevel, param.creditToken); Helper.ensureMaxLocking(collection, newVipLevel, param.expiryTs, param.nftIds.length, true); uint256 minMaintCredit = userAccount.minMaintCredit; uint256[] memory safeBoxKeys; { CollectionAccount storage collectionAccount = userAccount.getOrAddCollection(param.collection); // extend lock duration int256[] memory vipLevelDiffs; (vipLevelDiffs, totalCreditCost, safeBoxKeys) = _extendLockingForKeys(collection, collectionAccount, param.nftIds, param.expiryTs, uint8(newVipLevel)); // compute max credit for locking cost uint96 totalLockingCredit = collectionAccount.totalLockingCredit; { uint256 creditBuffer; unchecked { creditBuffer = totalCredit - totalLockingCredit; } if (totalCreditCost > creditBuffer || totalCreditCost > param.maxCreditCost) { revert Errors.InsufficientCredit(); } } // update user vip key counts for (uint256 vipLevel = 0; vipLevel < vipLevelDiffs.length;) { userAccount.updateVipKeyCount(uint8(vipLevel), vipLevelDiffs[vipLevel]); unchecked { ++vipLevel; } } totalLockingCredit += totalCreditCost.toUint96(); collectionAccount.totalLockingCredit = totalLockingCredit; if (totalLockingCredit > minMaintCredit) { userAccount.minMaintCredit = totalLockingCredit; minMaintCredit = totalLockingCredit; } } emit ExtendKey(msg.sender, param.collection, param.nftIds, safeBoxKeys, param.expiryTs, minMaintCredit); } function _extendLockingForKeys( CollectionState storage collectionState, CollectionAccount storage userCollectionAccount, uint256[] memory nftIds, uint256 newExpiryTs, // expiryTs of 0 is infinite. uint8 newVipLevel ) private returns (int256[] memory, uint256, uint256[] memory) { LockInfo memory info = LockInfo({ isInfinite: newExpiryTs == 0, currentBucket: Helper.counterStamp(block.timestamp), newExpiryBucket: Helper.counterStamp(newExpiryTs), selfLocked: userCollectionAccount.keyCnt, totalManaged: collectionState.activeSafeBoxCnt + collectionState.freeTokenIds.length, newRequireLockCredit: 0, infiniteCnt: collectionState.infiniteCnt }); if (info.isInfinite) { info.newExpiryBucket = Helper.counterStamp(block.timestamp + Constants.MAX_LOCKING_PERIOD); } uint256[] memory buckets = Helper.prepareBucketUpdate(collectionState, info.currentBucket, info.newExpiryBucket); int256[] memory vipLevelDiffs = new int256[](Constants.VIP_LEVEL_COUNT); /// @dev `keys` used to log info, we just compact its fields into one 256 bits number uint256[] memory keys = new uint256[](nftIds.length); for (uint256 idx; idx < nftIds.length;) { if ( Helper.hasActiveActivities(collectionState, nftIds[idx]) /// listing safebox can be extended && !Helper.hasActiveListOffer(collectionState, nftIds[idx]) ) { revert Errors.NftHasActiveActivities(); } (SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) = Helper.useSafeBoxAndKey(collectionState, userCollectionAccount, nftIds[idx]); { uint256 extendOffset = Helper.counterStamp(safeBox.expiryTs) - info.currentBucket; unchecked { for (uint256 i; i < extendOffset; ++i) { if (buckets[i] == 0) revert Errors.InvalidParam(); --buckets[i]; } } } uint256 safeboxQuote = updateCountersAndGetSafeboxCredit(buckets, info, newVipLevel); if (safeboxQuote > safeBoxKey.lockingCredit) { info.newRequireLockCredit += (safeboxQuote - safeBoxKey.lockingCredit); safeBoxKey.lockingCredit = safeboxQuote.toUint96(); } uint8 oldVipLevel = safeBoxKey.vipLevel; if (newVipLevel > oldVipLevel) { safeBoxKey.vipLevel = newVipLevel; --vipLevelDiffs[oldVipLevel]; ++vipLevelDiffs[newVipLevel]; } if (info.isInfinite) { safeBox.expiryTs = 0; ++info.infiniteCnt; } else { safeBox.expiryTs = uint32(newExpiryTs); } keys[idx] = SafeBoxLib.encodeSafeBoxKey(safeBoxKey); unchecked { ++idx; } } if (info.isInfinite) { collectionState.infiniteCnt = info.infiniteCnt; } else { collectionState.countingBuckets.batchSet(info.currentBucket, buckets); if (info.newExpiryBucket > collectionState.lastUpdatedBucket) { collectionState.lastUpdatedBucket = uint64(info.newExpiryBucket); } } return (vipLevelDiffs, info.newRequireLockCredit, keys); } function updateCountersAndGetSafeboxCredit(uint256[] memory counters, LockInfo memory lockInfo, uint8 vipLevel) private pure returns (uint256 result) { unchecked { uint256 infiniteCnt = lockInfo.infiniteCnt; uint256 totalManaged = lockInfo.totalManaged; uint256 counterOffsetEnd = (counters.length + 1) * 0x20; uint256 tmpCount; if (lockInfo.isInfinite) { for (uint256 i = 0x20; i < counterOffsetEnd; i += 0x20) { assembly { tmpCount := mload(add(counters, i)) } result += Constants.getRequiredStakingForLockRatio(infiniteCnt + tmpCount, totalManaged); } } else { for (uint256 i = 0x20; i < counterOffsetEnd; i += 0x20) { assembly { tmpCount := mload(add(counters, i)) } result += Constants.getRequiredStakingForLockRatio(infiniteCnt + tmpCount, totalManaged); assembly { /// increase counters[i] mstore(add(counters, i), add(tmpCount, 1)) } } result = Constants.getVipRequiredStakingWithDiscount(result, vipLevel); } result = Constants.getRequiredStakingWithSelfRatio( result, Helper.calculateSelfLockingRatio(lockInfo.selfLocked, totalManaged) ); } } function removeExpiredKeysAndRestoreCredits( CollectionState storage collectionState, UserFloorAccount storage account, address collectionId, uint256[] memory nftIds, address onBehalfOf, bool verifyLocking ) public returns (uint256 releasedCredit) { CollectionAccount storage collectionAccount = account.getByKey(collectionId); if (verifyLocking) { /// tricky logic, when verifying, we assume the keyCnt maybe incorrect and prevent it from underflow by increasing it collectionAccount.keyCnt = uint32(nftIds.length); } uint256 lockingCredit; uint256 removedCnt; uint256[] memory removedKeys = new uint256[](nftIds.length); for (uint256 i; i < nftIds.length;) { SafeBoxKey memory safeBoxKey = collectionAccount.getByKey(nftIds[i]); SafeBox memory safeBox = collectionState.safeBoxes[nftIds[i]]; if (safeBoxKey.keyId == 0) { revert Errors.InvalidParam(); } if (safeBox._isSafeBoxExpired() || !safeBox._isKeyMatchingSafeBox(safeBoxKey)) { /// reuse the array nftIds[removedCnt] = nftIds[i]; removedKeys[removedCnt] = SafeBoxLib.encodeSafeBoxKey(safeBoxKey); unchecked { ++removedCnt; releasedCredit += safeBoxKey.lockingCredit; } account.updateVipKeyCount(safeBoxKey.vipLevel, -1); collectionAccount.removeSafeboxKey(nftIds[i]); } else if (verifyLocking) { unchecked { lockingCredit += safeBoxKey.lockingCredit; } } unchecked { ++i; } } if (releasedCredit > 0) { collectionAccount.totalLockingCredit -= releasedCredit.toUint96(); } if (verifyLocking) { if (collectionAccount.totalLockingCredit != lockingCredit) revert Errors.InvalidParam(); /// when locking credit are matching, the amount of keys with credit locked can be computed directly /// the keys without credit locked can be ignored temporarily collectionAccount.keyCnt = (nftIds.length - removedCnt).toUint32(); } emit RemoveExpiredKey( msg.sender, onBehalfOf, collectionId, removedCnt == nftIds.length ? nftIds : Array.slice(nftIds, 0, removedCnt), removedCnt == nftIds.length ? removedKeys : Array.slice(removedKeys, 0, removedCnt) ); } function tidyExpiredNFTs(CollectionState storage collection, uint256[] memory nftIds, address collectionId) public { uint256 nftLen = nftIds.length; for (uint256 i; i < nftLen;) { uint256 nftId = nftIds[i]; SafeBox storage safeBox = Helper.useSafeBox(collection, nftId); if (!safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasNotExpire(); if (!Helper.isAuctionPeriodOver(safeBox)) revert Errors.AuctionHasNotCompleted(); /// remove expired safebox, and dump it to vault removeSafeBox(collection, nftId); collection.freeTokenIds.push(nftId); unchecked { ++i; } } emit ExpiredNftToVault(msg.sender, collectionId, nftIds); } function claimRandomNFT( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, mapping(address => FeeConfig) storage feeConfs, address collectionId, uint256 claimCnt, uint256 maxCreditCost, address receiver ) public returns (uint256 totalCreditCost) { if (claimCnt == 0 || collection.freeTokenIds.length < claimCnt) revert Errors.ClaimableNftInsufficient(); uint256 freeAmount = collection.freeTokenIds.length; uint256 totalManaged = collection.activeSafeBoxCnt + freeAmount; /// when locking ratio greater than xx%, stop random redemption if ( Helper.calculateLockingRatioRaw(freeAmount - claimCnt, totalManaged - claimCnt) > Constants.VAULT_REDEMPTION_MAX_LOKING_RATIO ) { revert Errors.ClaimableNftInsufficient(); } /// quota represented with 32 bits /// fragment token fee with 10^18 decimals bool useQuotaTx = (maxCreditCost >> 32) == 0; uint256[] memory selectedTokenIds; (selectedTokenIds, totalCreditCost) = pickRandomFromVault(collection, freeAmount, totalManaged, claimCnt, useQuotaTx); if (totalCreditCost > maxCreditCost) { revert Errors.InsufficientCredit(); } if (useQuotaTx) { UserFloorAccount storage userAccount = userAccounts[msg.sender]; Helper.checkAndUpdateVaultQuota(userAccount.getByKey(collectionId), -int32(uint32(totalCreditCost))); } else { distributeRedemptionFunds(userAccounts, feeConfs, address(collection.floorToken), claimCnt, totalCreditCost); } collection.floorToken.burn(msg.sender, Constants.FLOOR_TOKEN_AMOUNT * selectedTokenIds.length); ERC721Transfer.safeBatchTransferFrom(collectionId, address(this), receiver, selectedTokenIds); emit ClaimRandomNft(msg.sender, receiver, collectionId, selectedTokenIds, totalCreditCost); } function pickRandomFromVault( CollectionState storage collection, uint256 vaultCnt, uint256 totalManaged, uint256 pickCnt, bool useQuotaTax ) private returns (uint256[] memory pickedTokenIds, uint256 totalCost) { pickedTokenIds = new uint256[](pickCnt); while (pickCnt > 0) { { (uint256 fee, uint32 quota) = Constants.getVaultFeeAtLR(Helper.calculateLockingRatioRaw(vaultCnt, totalManaged)); if (useQuotaTax) totalCost += quota; else totalCost += fee; } uint256 freeLength = collection.freeTokenIds.length; /// just compute a deterministic random number uint256 chosenNftIdx = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, totalManaged))) % freeLength; unchecked { --pickCnt; --totalManaged; --vaultCnt; } pickedTokenIds[pickCnt] = collection.freeTokenIds[chosenNftIdx]; collection.freeTokenIds[chosenNftIdx] = collection.freeTokenIds[freeLength - 1]; collection.freeTokenIds.pop(); } } function distributeRedemptionFunds( mapping(address => UserFloorAccount) storage accounts, mapping(address => FeeConfig) storage feeConfs, address token, uint256 redeemCnt, uint256 totalPrice ) private { address protocolReceipt = feeConfs[token].vaultFee.receipt; address royaltyReceipt = feeConfs[token].royalty.receipt; /// a bit difference, for vault redemption with fee, we treat royaltyRate as bips of the Fragment Token amount of 1 NFT /// so the dust will be the incoming to the protocol uint256 royaltyRate = feeConfs[token].royalty.vault; uint256 royalty = redeemCnt * (Constants.FLOOR_TOKEN_AMOUNT / 10000 * royaltyRate); if (totalPrice < royalty) { royalty = totalPrice; } UserFloorAccount storage userAccount = accounts[msg.sender]; userAccount.transferToken(accounts[protocolReceipt], token, totalPrice - royalty, false); userAccount.transferToken(accounts[royaltyReceipt], token, royalty, false); } function getLockingBuckets(CollectionState storage collection, uint256 startTimestamp, uint256 endTimestamp) public view returns (uint256[] memory) { return Helper.prepareBucketUpdate( collection, Helper.counterStamp(startTimestamp), Math.min(collection.lastUpdatedBucket, Helper.counterStamp(endTimestamp)) ); } function addSafeBox(CollectionState storage collectionState, uint256 nftId, SafeBox memory safebox) internal { if (collectionState.safeBoxes[nftId].keyId > 0) revert Errors.SafeBoxAlreadyExist(); collectionState.safeBoxes[nftId] = safebox; ++collectionState.activeSafeBoxCnt; } function removeSafeBox(CollectionState storage collectionState, uint256 nftId) internal { delete collectionState.safeBoxes[nftId]; --collectionState.activeSafeBoxCnt; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "../Constants.sol"; import "../Errors.sol"; import "./SafeBox.sol"; import "./User.sol"; import { SafeBox, CollectionState, AuctionInfo, CollectionAccount, SafeBoxKey, FeeRate, PrivateOffer, Fees } from "./Structs.sol"; import "../library/RollingBuckets.sol"; library Helper { using SafeBoxLib for SafeBox; using UserLib for CollectionAccount; using RollingBuckets for mapping(uint256 => uint256); function counterStamp(uint256 timestamp) internal pure returns (uint96) { unchecked { return uint96((timestamp + Constants.BUCKET_SPAN_1) / Constants.BUCKET_SPAN); } } function checkCollectionSafeboxQuota( CollectionAccount storage account, CollectionState storage collection, uint256 newLocked ) internal view { uint256 selfRatio = calculateSelfLockingRatio( account.keyCnt + newLocked, collection.activeSafeBoxCnt + collection.freeTokenIds.length + newLocked ); if (selfRatio > Constants.USER_COLLECTION_LOCKED_BOUND_PCT) revert Errors.UserQuotaExhausted(); } function checkAndUpdateUserSafeboxQuota(UserFloorAccount storage account, uint8 vipLevel, uint16 newLocked) internal { uint16 used = updateUserSafeboxQuota(account); uint16 totalQuota = Constants.getSafeboxPeriodQuota(vipLevel); uint16 nextUsed = used + newLocked; if (nextUsed > totalQuota) revert Errors.PeriodQuotaExhausted(); account.safeboxQuotaUsed = nextUsed; checkSafeboxQuotaGlobal(account, vipLevel, newLocked); } function checkSafeboxQuotaGlobal(UserFloorAccount storage account, uint8 vipLevel, uint256 newLocked) internal view { uint256 totalQuota = Constants.getSafeboxUserQuota(vipLevel); if (totalQuota < newLocked) { revert Errors.UserQuotaExhausted(); } else { unchecked { totalQuota -= newLocked; } } (, uint256[] memory keyCnts) = UserLib.getMinLevelAndVipKeyCounts(account.vipInfo); for (uint256 i; i < Constants.VIP_LEVEL_COUNT;) { if (totalQuota >= keyCnts[i]) { unchecked { totalQuota -= keyCnts[i]; } } else { revert Errors.UserQuotaExhausted(); } unchecked { ++i; } } } function ensureProxyVipLevel(uint8 vipLevel, bool proxy) internal pure { if (proxy && vipLevel < Constants.PROXY_COLLECTION_VIP_THRESHOLD) { revert Errors.InvalidParam(); } } function ensureMaxLocking( CollectionState storage collection, uint8 vipLevel, uint256 requireExpiryTs, uint256 requireLockCnt, bool extend ) internal view { /// vip level 0 can not use safebox utilities. if (vipLevel >= Constants.VIP_LEVEL_COUNT || vipLevel == 0) { revert Errors.InvalidParam(); } uint256 lockingRatio = calculateLockingRatio(collection, requireLockCnt); uint256 restrictRatio; if (extend) { /// try to extend exist safebox /// only restrict infinity locking, normal safebox with expiry should be skipped restrictRatio = requireExpiryTs == 0 ? Constants.getLockingRatioForInfinite(vipLevel) : 100; } else { /// try to lock(create new safebox) /// restrict maximum locking ratio to use safebox restrictRatio = Constants.getLockingRatioForSafebox(vipLevel); if (requireExpiryTs == 0) { uint256 extraRatio = Constants.getLockingRatioForInfinite(vipLevel); if (restrictRatio > extraRatio) restrictRatio = extraRatio; } } if (lockingRatio > restrictRatio) revert Errors.InvalidParam(); /// only check when it is not infinite lock if (requireExpiryTs > 0) { uint256 deltaBucket; unchecked { deltaBucket = counterStamp(requireExpiryTs) - counterStamp(block.timestamp); } if (deltaBucket == 0 || deltaBucket > Constants.getVipLockingBuckets(vipLevel)) { revert Errors.InvalidParam(); } } } function useSafeBoxAndKey(CollectionState storage collection, CollectionAccount storage userAccount, uint256 nftId) internal view returns (SafeBox storage safeBox, SafeBoxKey storage key) { safeBox = collection.safeBoxes[nftId]; if (safeBox.keyId == 0) revert Errors.SafeBoxNotExist(); if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire(); key = userAccount.getByKey(nftId); if (!safeBox.isKeyMatchingSafeBox(key)) revert Errors.NoMatchingSafeBoxKey(); } function useSafeBox(CollectionState storage collection, uint256 nftId) internal view returns (SafeBox storage safeBox) { safeBox = collection.safeBoxes[nftId]; if (safeBox.keyId == 0) revert Errors.SafeBoxNotExist(); } function generateNextKeyId(CollectionState storage collectionState) internal returns (uint64 nextKeyId) { nextKeyId = collectionState.nextKeyId; ++collectionState.nextKeyId; } function generateNextActivityId(CollectionState storage collection) internal returns (uint64 nextActivityId) { nextActivityId = collection.nextActivityId; ++collection.nextActivityId; } function isAuctionPeriodOver(SafeBox storage safeBox) internal view returns (bool) { return safeBox.expiryTs + Constants.FREE_AUCTION_PERIOD < block.timestamp; } function hasActiveActivities(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return hasActiveAuction(collection, nftId) || hasActiveRaffle(collection, nftId) || hasActivePrivateOffer(collection, nftId) || hasActiveListOffer(collection, nftId); } function hasActiveAuction(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return collection.activeAuctions[nftId].endTime >= block.timestamp; } function hasActiveRaffle(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return collection.activeRaffles[nftId].endTime >= block.timestamp; } function hasActivePrivateOffer(CollectionState storage collection, uint256 nftId) internal view returns (bool) { return collection.activePrivateOffers[nftId].endTime >= block.timestamp; } function hasActiveListOffer(CollectionState storage collection, uint256 nftId) internal view returns (bool) { PrivateOffer storage offer = collection.activePrivateOffers[nftId]; return offer.activityId > 0 && offer.buyer == address(0) && !useSafeBox(collection, nftId).isSafeBoxExpired(); } function calculateActivityFee(uint256 settleAmount, uint256 feeRateBips) internal pure returns (uint256 afterFee, uint256 fee) { fee = settleAmount * feeRateBips / 10000; unchecked { afterFee = settleAmount - fee; } } function prepareBucketUpdate(CollectionState storage collection, uint256 startBucket, uint256 endBucket) internal view returns (uint256[] memory buckets) { uint256 validEnd = collection.lastUpdatedBucket; uint256 padding; if (endBucket < validEnd) { validEnd = endBucket; } else { unchecked { padding = endBucket - validEnd; } } if (startBucket < validEnd) { if (padding == 0) { buckets = collection.countingBuckets.batchGet(startBucket, validEnd); } else { uint256 validLen; unchecked { validLen = validEnd - startBucket; } buckets = new uint256[](validLen + padding); uint256[] memory tmp = collection.countingBuckets.batchGet(startBucket, validEnd); for (uint256 i; i < validLen;) { buckets[i] = tmp[i]; unchecked { ++i; } } } } else { buckets = new uint256[](endBucket - startBucket); } } function getActiveSafeBoxes(CollectionState storage collectionState, uint256 timestamp) internal view returns (uint256) { uint256 bucketStamp = counterStamp(timestamp); if (collectionState.lastUpdatedBucket < bucketStamp) { return 0; } return collectionState.countingBuckets.get(bucketStamp); } function calculateLockingRatio(CollectionState storage collection, uint256 newLocked) internal view returns (uint256) { uint256 freeAmount = collection.freeTokenIds.length; uint256 totalManaged = newLocked + collection.activeSafeBoxCnt + freeAmount; return calculateLockingRatioRaw(freeAmount, totalManaged); } function calculateLockingRatioRaw(uint256 freeAmount, uint256 totalManaged) internal pure returns (uint256) { if (totalManaged == 0) { return 0; } else { unchecked { return (100 - freeAmount * 100 / totalManaged); } } } function calculateSelfLockingRatio(uint256 selfLocked, uint256 collectionTatalManaged) internal pure returns (uint256) { if (collectionTatalManaged == 0) { return 0; } else { unchecked { return (selfLocked * 100 / collectionTatalManaged); } } } function updateUserSafeboxQuota(UserFloorAccount storage account) internal returns (uint16) { if (block.timestamp - account.lastQuotaPeriodTs <= Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION) { return account.safeboxQuotaUsed; } else { unchecked { account.lastQuotaPeriodTs = uint32( block.timestamp / Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION * Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION ); } account.safeboxQuotaUsed = 0; return 0; } } function applyDiffToCounters( CollectionState storage collectionState, uint256 startBucket, uint256 endBucket, int256 diff ) internal { if (startBucket == endBucket) return; uint256[] memory buckets = Helper.prepareBucketUpdate(collectionState, startBucket, endBucket); unchecked { uint256 bucketLen = buckets.length; if (diff > 0) { uint256 tmp = uint256(diff); for (uint256 i; i < bucketLen; ++i) { buckets[i] += tmp; } } else { uint256 tmp = uint256(-diff); for (uint256 i; i < bucketLen; ++i) { buckets[i] -= tmp; } } } collectionState.countingBuckets.batchSet(startBucket, buckets); if (endBucket > collectionState.lastUpdatedBucket) { collectionState.lastUpdatedBucket = uint64(endBucket); } } function computeFees(uint256 price, Fees memory fees) internal pure returns (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) { protocolFee = price * fees.protocol.rateBips / 10_000; royalty = price * fees.royalty.rateBips / 10_000; unchecked { priceWithoutFee = price - protocolFee - royalty; } } function checkAndUpdateVaultQuota(CollectionAccount storage account, int32 diff) internal { if (block.timestamp - account.lastVaultActiveTs > Constants.VAULT_QUOTA_RESET_PERIOD) { // exceeds the reset period if (diff < 0) revert Errors.UserQuotaExhausted(); else account.vaultContQuota = uint32(diff); } else { int32 res = int32(account.vaultContQuota) + diff; if (res < 0) revert Errors.UserQuotaExhausted(); else account.vaultContQuota = uint32(res); } if (diff > 0) { /// only refresh when increasing quota account.lastVaultActiveTs = uint32(block.timestamp); } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import {SafeBox, SafeBoxKey} from "./Structs.sol"; library SafeBoxLib { uint64 public constant SAFEBOX_KEY_NOTATION = type(uint64).max; function isInfiniteSafeBox(SafeBox storage safeBox) internal view returns (bool) { return safeBox.expiryTs == 0; } function isSafeBoxExpired(SafeBox storage safeBox) internal view returns (bool) { return safeBox.expiryTs != 0 && safeBox.expiryTs < block.timestamp; } function _isSafeBoxExpired(SafeBox memory safeBox) internal view returns (bool) { return safeBox.expiryTs != 0 && safeBox.expiryTs < block.timestamp; } function isKeyMatchingSafeBox(SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) internal view returns (bool) { return safeBox.keyId == safeBoxKey.keyId; } function _isKeyMatchingSafeBox(SafeBox memory safeBox, SafeBoxKey memory safeBoxKey) internal pure returns (bool) { return safeBox.keyId == safeBoxKey.keyId; } function encodeSafeBoxKey(SafeBoxKey memory key) internal pure returns (uint256) { uint256 val = key.lockingCredit; val |= (uint256(key.keyId) << 96); val |= (uint256(key.vipLevel) << 160); return val; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "../interface/IFragmentToken.sol"; struct SafeBox { /// Either matching a key OR Constants.SAFEBOX_KEY_NOTATION meaning temporarily /// held by a bidder in auction. uint64 keyId; /// The timestamp that the safe box expires. uint32 expiryTs; /// The owner of the safebox. It maybe outdated due to expiry address owner; } struct PrivateOffer { /// private offer end time uint96 endTime; /// which token used to accpet the offer address token; /// price of the offer uint96 price; address owner; /// who should receive the offer address buyer; uint64 activityId; Fees fees; } enum AuctionType { Owned, Expired, Vault } struct AuctionInfo { /// The end time for the auction. uint96 endTime; /// Bid token address. address bidTokenAddress; /// Minimum Bid. uint96 minimumBid; /// The person who trigger the auction at the beginning. address triggerAddress; uint96 lastBidAmount; address lastBidder; /// [Deprecated] Whether the auction is triggered by the NFT owner itself? /// Note. Don't remove it directly as we need keep mainnet contract layout bool isSelfTriggered; uint64 activityId; /// [Deprecated] fee config /// Note. Don't remove it directly as we need keep mainnet contract layout uint32 oldFeeRateBips; AuctionType typ; Fees fees; } struct TicketRecord { /// who buy the tickets address buyer; /// Start index of tickets /// [startIdx, endIdx) uint48 startIdx; /// End index of tickets uint48 endIdx; } struct RaffleInfo { /// raffle end time uint48 endTime; /// max tickets amount the raffle can sell uint48 maxTickets; /// which token used to buy the raffle tickets address token; /// owner of raffle address owner; /// price per ticket uint96 ticketPrice; uint64 activityId; /// total funds collected by selling tickets uint96 collectedFund; /// total sold tickets amount uint48 ticketSold; /// whether the raffle is being settling bool isSettling; /// tickets sold records TicketRecord[] tickets; Fees fees; } struct CollectionState { /// The address of the Floor Token cooresponding to the NFTs. IFragmentToken floorToken; /// Records the active safe box in each time bucket. mapping(uint256 => uint256) countingBuckets; /// Stores all of the NFTs that has been fragmented but *without* locked up limit. uint256[] freeTokenIds; /// Huge map for all the `SafeBox`es in one collection. mapping(uint256 => SafeBox) safeBoxes; /// Stores all the ongoing auctions: nftId => `AuctionInfo`. mapping(uint256 => AuctionInfo) activeAuctions; /// Stores all the ongoing raffles: nftId => `RaffleInfo`. mapping(uint256 => RaffleInfo) activeRaffles; /// Stores all the ongoing private offers: nftId => `PrivateOffer`. mapping(uint256 => PrivateOffer) activePrivateOffers; /// The last bucket time the `countingBuckets` is updated. uint64 lastUpdatedBucket; /// Next Key Id. This should start from 1, we treat key id `SafeboxLib.SAFEBOX_KEY_NOTATION` as temporarily /// being used for activities(auction/raffle). uint64 nextKeyId; /// Active Safe Box Count. uint64 activeSafeBoxCnt; /// The number of infinite lock count. uint64 infiniteCnt; /// Next Activity Id. This should start from 1 uint64 nextActivityId; uint32 lastVaultAuctionPeriodTs; } struct UserFloorAccount { /// @notice it should be maximum of the `totalLockingCredit` across all collections uint96 minMaintCredit; /// @notice used to iterate collection accounts /// packed with `minMaintCredit` to reduce storage slot access address firstCollection; /// @notice user vip level related info /// 0 - 239 bits: store SafeBoxKey Count per vip level, per level using 24 bits /// 240 - 247 bits: store minMaintVipLevel /// 248 - 255 bits: remaining uint256 vipInfo; /// @notice Locked Credit amount which cannot be withdrawn and will be released as time goes. uint256 lockedCredit; mapping(address => CollectionAccount) accounts; mapping(address => uint256) tokenAmounts; /// Each account has safebox quota to use per period uint32 lastQuotaPeriodTs; uint16 safeboxQuotaUsed; /// [Deprecated] Each account has vault redemption waiver per period uint32 lastWaiverPeriodTs; uint96 creditWaiverUsed; } struct SafeBoxKey { /// locked credit amount of this safebox uint96 lockingCredit; /// corresponding key id of the safebox uint64 keyId; /// which vip level the safebox locked uint8 vipLevel; } struct CollectionAccount { mapping(uint256 => SafeBoxKey) keys; /// total locking credit of all `keys` in this collection uint96 totalLockingCredit; /// track next collection as linked list address next; /// tracking total locked of the collection uint32 keyCnt; /// Depositing to vault gets quota, redepmtion consumes quota uint32 vaultContQuota; /// Used to track and clear vault contribution quota when the quota is inactive for a certain duration uint32 lastVaultActiveTs; } struct Fees { FeeRate royalty; FeeRate protocol; } struct FeeConfig { RoyaltyFeeRate royalty; SafeboxFeeRate safeboxFee; VaultFeeRate vaultFee; } struct RoyaltyFeeRate { address receipt; uint16 marketlist; uint16 vault; uint16 raffle; } struct VaultFeeRate { address receipt; uint16 vaultAuction; } struct SafeboxFeeRate { address receipt; uint16 auctionOwned; uint16 auctionExpired; uint16 raffle; uint16 marketlist; } struct FeeRate { address receipt; uint16 rateBips; } /// Internal Structure struct LockParam { address proxyCollection; address collection; uint256[] nftIds; uint256 expiryTs; uint8 vipLevel; uint256 maxCreditCost; address creditToken; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "../library/CurrencyTransfer.sol"; import "../Constants.sol"; import "../Errors.sol"; import {UserFloorAccount, CollectionAccount, SafeBoxKey} from "./Structs.sol"; library UserLib { using SafeCast for uint256; /// @notice `sender` deposit `token` into Flooring on behalf of `receiver`. `receiver`'s account will be updated. event DepositToken(address indexed sender, address indexed receiver, address indexed token, uint256 amount); /// @notice `sender` withdraw `token` from Flooring and transfer it to `receiver`. event WithdrawToken(address indexed sender, address indexed receiver, address indexed token, uint256 amount); /// @notice update the account maintain credit on behalfOf `onBehalfOf` event UpdateMaintainCredit(address indexed onBehalfOf, uint256 minMaintCredit); address internal constant LIST_GUARD = address(1); function ensureVipCredit(UserFloorAccount storage account, uint8 requireVipLevel, address creditToken) internal view returns (uint256) { uint256 totalCredit = tokenBalance(account, creditToken); if (Constants.getVipBalanceRequirements(requireVipLevel) > totalCredit) { revert Errors.InsufficientBalanceForVipLevel(); } return totalCredit; } function getMinMaintVipLevel(UserFloorAccount storage account) internal view returns (uint8) { unchecked { return uint8(account.vipInfo >> 240); } } function getMinLevelAndVipKeyCounts(uint256 vipInfo) internal pure returns (uint8 minLevel, uint256[] memory counts) { unchecked { counts = new uint256[](Constants.VIP_LEVEL_COUNT); minLevel = uint8(vipInfo >> 240); for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ++i) { counts[i] = (vipInfo >> (i * 24)) & 0xFFFFFF; } } } function storeMinLevelAndVipKeyCounts( UserFloorAccount storage account, uint8 minMaintVipLevel, uint256[] memory keyCounts ) internal { unchecked { uint256 _data = (uint256(minMaintVipLevel) << 240); for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ++i) { _data |= ((keyCounts[i] & 0xFFFFFF) << (i * 24)); } account.vipInfo = _data; } } function getOrAddCollection(UserFloorAccount storage user, address collection) internal returns (CollectionAccount storage) { CollectionAccount storage entry = user.accounts[collection]; if (entry.next == address(0)) { if (user.firstCollection == address(0)) { user.firstCollection = collection; entry.next = LIST_GUARD; } else { entry.next = user.firstCollection; user.firstCollection = collection; } } return entry; } function removeCollection(UserFloorAccount storage userAccount, address collection, address prev) internal { CollectionAccount storage cur = userAccount.accounts[collection]; if (cur.next == address(0)) revert Errors.InvalidParam(); if (collection == userAccount.firstCollection) { if (cur.next == LIST_GUARD) { userAccount.firstCollection = address(0); } else { userAccount.firstCollection = cur.next; } } else { CollectionAccount storage prevAccount = userAccount.accounts[prev]; if (prevAccount.next != collection) revert Errors.InvalidParam(); prevAccount.next = cur.next; } delete userAccount.accounts[collection]; } function getByKey(UserFloorAccount storage userAccount, address collection) internal view returns (CollectionAccount storage) { return userAccount.accounts[collection]; } function addSafeboxKey(CollectionAccount storage account, uint256 nftId, SafeBoxKey memory key) internal { if (account.keys[nftId].keyId > 0) { revert Errors.SafeBoxKeyAlreadyExist(); } ++account.keyCnt; account.keys[nftId] = key; } function removeSafeboxKey(CollectionAccount storage account, uint256 nftId) internal { --account.keyCnt; delete account.keys[nftId]; } function getByKey(CollectionAccount storage account, uint256 nftId) internal view returns (SafeBoxKey storage) { return account.keys[nftId]; } function tokenBalance(UserFloorAccount storage account, address token) internal view returns (uint256) { return account.tokenAmounts[token]; } function lockCredit(UserFloorAccount storage account, uint256 amount) internal { unchecked { account.lockedCredit += amount; } } function unlockCredit(UserFloorAccount storage account, uint256 amount) internal { unchecked { account.lockedCredit -= amount; } } function deposit( UserFloorAccount storage account, address onBehalfOf, address token, uint256 amount, bool isLockCredit ) public { depositToken(account, token, amount); if (isLockCredit) lockCredit(account, amount); if (token == CurrencyTransfer.NATIVE) { require(amount == msg.value); } else { CurrencyTransfer.safeTransferFrom(token, msg.sender, address(this), amount); } emit DepositToken(msg.sender, onBehalfOf, token, amount); } function withdraw(UserFloorAccount storage account, address receiver, address token, uint256 amount, bool isCredit) public { withdrawToken(account, token, amount, isCredit); CurrencyTransfer.safeTransfer(token, receiver, amount); emit WithdrawToken(msg.sender, receiver, token, amount); } function depositToken(UserFloorAccount storage account, address token, uint256 amount) internal { account.tokenAmounts[token] += amount; } function withdrawToken(UserFloorAccount storage account, address token, uint256 amount, bool isCreditToken) internal { uint256 balance = account.tokenAmounts[token]; if (balance < amount) { revert Errors.InsufficientCredit(); } if (isCreditToken) { uint256 avaiableBuf; unchecked { avaiableBuf = balance - amount; } if ( avaiableBuf < Constants.getVipBalanceRequirements(getMinMaintVipLevel(account)) || avaiableBuf < account.minMaintCredit || avaiableBuf < account.lockedCredit ) { revert Errors.InsufficientCredit(); } account.tokenAmounts[token] = avaiableBuf; } else { unchecked { account.tokenAmounts[token] = balance - amount; } } } function transferToken( UserFloorAccount storage from, UserFloorAccount storage to, address token, uint256 amount, bool isCreditToken ) internal { if (amount > 0) { withdrawToken(from, token, amount, isCreditToken); depositToken(to, token, amount); } } function updateVipKeyCount(UserFloorAccount storage account, uint8 vipLevel, int256 diff) internal { if (vipLevel > 0 && diff != 0) { (uint8 minMaintVipLevel, uint256[] memory keyCounts) = getMinLevelAndVipKeyCounts(account.vipInfo); if (diff < 0) { keyCounts[vipLevel] -= uint256(-diff); if (vipLevel == minMaintVipLevel && keyCounts[vipLevel] == 0) { uint8 newVipLevel = vipLevel; do { unchecked { --newVipLevel; } } while (newVipLevel > 0 && keyCounts[newVipLevel] == 0); minMaintVipLevel = newVipLevel; } } else { keyCounts[vipLevel] += uint256(diff); if (vipLevel > minMaintVipLevel) { minMaintVipLevel = vipLevel; } } storeMinLevelAndVipKeyCounts(account, minMaintVipLevel, keyCounts); } } function recalculateMinMaintCredit(UserFloorAccount storage account, address onBehalfOf) public returns (uint256 maxLocking) { address prev = account.firstCollection; for (address collection = account.firstCollection; collection != LIST_GUARD && collection != address(0);) { (uint256 locking, address next) = (getByKey(account, collection).totalLockingCredit, getByKey(account, collection).next); if (locking == 0) { removeCollection(account, collection, prev); collection = next; } else { if (locking > maxLocking) { maxLocking = locking; } prev = collection; collection = next; } } account.minMaintCredit = uint96(maxLocking); emit UpdateMaintainCredit(onBehalfOf, maxLocking); } }
{ "metadata": { "bytecodeHash": "none", "useLiteralContent": true }, "optimizer": { "enabled": true, "runs": 800 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"name":"AuctionBidIsNotHighEnough","type":"error"},{"inputs":[],"name":"AuctionHasExpire","type":"error"},{"inputs":[],"name":"AuctionHasNotCompleted","type":"error"},{"inputs":[],"name":"AuctionInvalidBidAmount","type":"error"},{"inputs":[],"name":"AuctionNotExist","type":"error"},{"inputs":[],"name":"AuctionSelfBid","type":"error"},{"inputs":[],"name":"BucketLengthExceedsLimit","type":"error"},{"inputs":[],"name":"BucketValueExceedsLimit","type":"error"},{"inputs":[],"name":"InsufficientBalanceForVipLevel","type":"error"},{"inputs":[],"name":"InsufficientCredit","type":"error"},{"inputs":[],"name":"InvalidParam","type":"error"},{"inputs":[],"name":"NftHasActiveActivities","type":"error"},{"inputs":[],"name":"NoMatchingSafeBoxKey","type":"error"},{"inputs":[],"name":"PeriodQuotaExhausted","type":"error"},{"inputs":[],"name":"SafeBoxAlreadyExist","type":"error"},{"inputs":[],"name":"SafeBoxAuctionWindowHasPassed","type":"error"},{"inputs":[],"name":"SafeBoxHasExpire","type":"error"},{"inputs":[],"name":"SafeBoxHasNotExpire","type":"error"},{"inputs":[],"name":"SafeBoxKeyAlreadyExist","type":"error"},{"inputs":[],"name":"SafeBoxNotExist","type":"error"},{"inputs":[{"internalType":"uint8","name":"bits","type":"uint8"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeCastOverflowedUintDowncast","type":"error"},{"inputs":[],"name":"TokenNotSupported","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"winner","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64","name":"activityId","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxKeyId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collectedFunds","type":"uint256"}],"name":"AuctionEnded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trigger","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64[]","name":"activityIds","type":"uint64[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"enum AuctionType","name":"typ","type":"uint8"},{"components":[{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"royalty","type":"tuple"},{"components":[{"internalType":"address","name":"receipt","type":"address"},{"internalType":"uint16","name":"rateBips","type":"uint16"}],"internalType":"struct FeeRate","name":"protocol","type":"tuple"}],"indexed":false,"internalType":"struct Fees","name":"fees","type":"tuple"},{"indexed":false,"internalType":"address","name":"settleToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"minimumBid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionEndTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"}],"name":"AuctionStartedV2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"bidder","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint64","name":"activityId","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bidAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionEndTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"}],"name":"NewTopBidOnAuction","type":"event"}]
Contract Creation Code
61361e610035600b8282823980515f1a60731461002957634e487b7160e01b5f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040526004361061006f575f3560e01c80631c1a270f116100585780631c1a270f146100b35780635b20b5c6146100d2578063ba56df4e146100f1575f80fd5b806303e4298f146100735780630ffcb2ba14610094575b5f80fd5b81801561007e575f80fd5b5061009261008d36600461302e565b610110565b005b81801561009f575f80fd5b506100926100ae3660046130bd565b6103d7565b8180156100be575f80fd5b506100926100cd366004613155565b6105b0565b8180156100dd575f80fd5b506100926100ec3660046131fa565b610cc9565b8180156100fc575f80fd5b5061009261010b366004613256565b610fc5565b60018601546001600160a01b031661013b57604051633dd1b30560e01b815260040160405180910390fd5b5f610146895f611392565b90505f610152826113de565b5090508083101561017657604051636f5353b160e11b815260040160405180910390fd5b5050610180612edb565b61018d62015180426132c6565b6001600160601b031681526001600160a01b03831660208201526101b082611469565b6001600160601b031660408201523360608201526101cd82611469565b6001600160601b031660808201523360a08201526001610120820181905250604080516080810182525f818301818152606083018290528252825180840190935260018a01546001600160a01b0381168452600160b01b900461ffff166020848101919091528201929092526101408301528061024b8b87856114a4565b63ffffffff16915091505f86515f61026391906132ed565b9050886001600160a01b0316866001600160a01b0316036102e557305f90815260208c90526040902087516102e09190889084906102a1908a6132ed565b6102ab91906132c6565b60018f5f336001600160a01b03166001600160a01b031681526020019081526020015f2061183690949392919063ffffffff16565b610361565b305f90815260208c905260409020875161033a9190889061030690896132ed565b5f8f5f336001600160a01b03166001600160a01b031681526020019081526020015f2061183690949392919063ffffffff16565b801561036157305f90815260208c905260408082203383529120610361918b846001611836565b876001600160a01b0316336001600160a01b03167fd6633dc7eec5401de6894895436c2b403611ed125cdde2fa99e0b362ef9bef8c858a8861012001518961014001518c8c8c5f01518b8b6040516103c1999897969594939291906133da565b60405180910390a3505050505050505050505050565b60018701546001600160a01b031661040257604051633dd1b30560e01b815260040160405180910390fd5b335f90815260208990526040812085519091906104289068056bc75e2d631000006132ed565b305f90815260208c9052604090209091506104489083908a846001611836565b610450612edb565b61045d62015180426132c6565b6001600160601b031681526001600160a01b038516602082015261048084611469565b6001600160601b031660408201523360608201525f6080820181905260a08201819052610120820181905250604080516080810182525f818301818152606083018290528252825180840190935260018d01546001600160a01b0381168452600160a01b900461ffff166020848101919091528201929092526101408301528061052e8e610526878d6001600160a01b03165f9081526003919091016020526040902090565b8b8b87611853565b63ffffffff1691509150896001600160a01b0316336001600160a01b03167fd6633dc7eec5401de6894895436c2b403611ed125cdde2fa99e0b362ef9bef8c848c8761012001518861014001518d8d8b5f01518a8e604051610598999897969594939291906133da565b60405180910390a35050505050505050505050505050565b82516001146105d257604051633494a40d60e21b815260040160405180910390fd5b60028601546001600160a01b03166105fd57604051633dd1b30560e01b815260040160405180910390fd5b5f610608895f611392565b90505f61061482611d8f565b60088b0154909150819061063590600160401b900463ffffffff1642613479565b116106535760405163d6516b8960e01b815260040160405180910390fd5b5f61065d836113de565b50905080846001600160601b0316101561068a57604051636f5353b160e11b815260040160405180910390fd5b505050610695612edb565b6106a262015180426132c6565b6001600160601b0390811682526001600160a01b03848116602080850191909152848316604080860182905233606080880182905260808089019490945260a088019190915260026101208801819052825193840183528d5480871685850190815261ffff600160b01b909204821686850152855283518085018552918f01549687168252600160a01b9096049095168585015282840194909452610140860191909152805192830190526001600160401b03825283515f939183019161076e916202a30091166132c6565b63ffffffff168152602001306001600160a01b031681525090505f85516001600160401b038111156107a2576107a2612f83565b6040519080825280602002602001820160405280156107cb578160200160208202803683370190505b5086519091505b8015610b8f5760028c015487515f19909201918890839081106107f7576107f761348c565b60200260200101511061081d57604051633494a40d60e21b815260040160405180910390fd5b5f8c6002018883815181106108345761083461348c565b60200260200101518154811061084c5761084c61348c565b5f9182526020909120015490506108648d8286611de7565b61086d8d611ed3565b8560e001906001600160401b031690816001600160401b031681525050848d6004015f8381526020019081526020015f205f820151815f015f6101000a8154816001600160601b0302191690836001600160601b031602179055506020820151815f01600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816001015f6101000a8154816001600160601b0302191690836001600160601b03160217905550606082015181600101600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506080820151816002015f6101000a8154816001600160601b0302191690836001600160601b0316021790555060a082015181600201600c6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060c0820151816003015f6101000a81548160ff02191690831515021790555060e08201518160030160016101000a8154816001600160401b0302191690836001600160401b031602179055506101008201518160030160096101000a81548163ffffffff021916908363ffffffff16021790555061012082015181600301600d6101000a81548160ff02191690836002811115610a4557610a456132d9565b021790555061014091909101518051805160048401805460209384015161ffff908116600160a01b9081026001600160b01b03199384166001600160a01b039687161717909355948401518051600590970180549190950151909516909102931693169290921717905560e08501518351849084908110610ac857610ac861348c565b6001600160401b039092166020928302919091019091015260028d018054610af290600190613479565b81548110610b0257610b0261348c565b905f5260205f2001548d600201898481518110610b2157610b2161348c565b602002602001015181548110610b3957610b3961348c565b5f9182526020909120015560028d01805480610b5757610b576134a0565b600190038181905f5260205f20015f9055905580888381518110610b7d57610b7d61348c565b602002602001018181525050506107d2565b50305f90815260208b81526040909120908401518751610be3929190610bbe906001600160601b0389166132ed565b335f90815260208f9052604090209291906001600160a01b038a8116908e1614611836565b610c298b6203f4806203f47f4201046001600160601b0316610c19856020015163ffffffff166203f4806203f47f919091010490565b6001600160601b03168951611f16565b428b60080160086101000a81548163ffffffff021916908363ffffffff160217905550866001600160a01b0316336001600160a01b03167fd6633dc7eec5401de6894895436c2b403611ed125cdde2fa99e0b362ef9bef8c838987610120015188610140015189602001518b8b5f01518b602001515f604051610cb4999897969594939291906134b4565b60405180910390a35050505050505050505050565b5f805f610cd584611ffc565b604080820151335f90815260208d905291909120919250610cf791908a6120e3565b50610d408a6040518060a00160405280898152602001610d1689611469565b6001600160601b031681523360208083019190915285516040830152850151606090910152612133565b5f88815260048d016020908152604080832081516101608101835281546001600160601b038082168352600160601b918290046001600160a01b039081169684019690965260018401548082169584019590955293819004851660608301526002808401549485166080840152930490931660a0840152600381015460ff808216151560c08601526101008083046001600160401b031660e08701526901000000000000000000830463ffffffff16908601526fffffffffffffffffffffffffffffffff97909716995094975092955090939192610120850192600160681b9091041690811115610e3357610e336132d9565b6002811115610e4457610e446132d9565b81526040805160808101825260048401546001600160a01b0380821683850190815261ffff600160a01b938490048116606086015290845284518086018652600590970154808316885292909204909116602086810191909152828101959095529284015283830151305f908152938e9052818420338552919093209394509192610edb92909184908a90828116908f1614611836565b8315610f0f576001600160a01b0383165f90815260208b90526040808220308352908220610f0f9290919084908890611836565b5f87815260038c01602090815260409182902082516060808201855291546001600160401b03808216835263ffffffff600160401b8304168386018190526001600160a01b03600160601b90930483168488015260e0890151895188519190931681529586018e90529585018c90526001600160601b03169284019290925260808301939093528a169033907f803010fdbef76f36b5d3e5ac091de314dede9c52855c46fd5e8c0b040961a40c9060a0016103c1565b5f5b815181101561138b575f828281518110610fe357610fe361348c565b602002602001015190505f610ff88783612437565b905061100381612474565b1561102157604051631d80cfbf60e21b815260040160405180910390fd5b5f828152600488016020908152604080832081516101608101835281546001600160601b038082168352600160601b918290046001600160a01b039081169684019690965260018401548082169584019590955293819004851660608301526002808401549485166080840152930490931660a0840152600381015460ff808216151560c08601526101008083046001600160401b031660e08701526901000000000000000000830463ffffffff16908601529192610120850192600160681b909204909116908111156110f7576110f76132d9565b6002811115611108576111086132d9565b81526040805160808101825260048401546001600160a01b0380821683850190815261ffff600160a01b9384900481166060860152908452845180860190955260059096015490811684520490931660208281019190915283810191909152015280519091506001600160601b03165f0361119657604051631cacacff60e11b815260040160405180910390fd5b42815f01516001600160601b031611156111c357604051630a0619fd60e01b815260040160405180910390fd5b60a08101516001600160a01b03166111ee57604051630a0619fd60e01b815260040160405180910390fd5b815461120d9088908390600160601b90046001600160a01b03166124a6565b60a081015160408051606081019091525f808252906020810161122f8c61257e565b6001600160401b0390811682525f602092830181905283830151885492166bffffffff000000000000000090921691909117600160601b6001600160a01b0387811691820292909217895582528c83526040808320918d16835260038201909352919020919250906112a28188856125a0565b5f87815260048d8101602090815260408084208481556001810185905560028101949094556003840180546dffffffffffffffffffffffffffff1916905591830180546001600160b01b03199081169091556005909301805490931690925560e0870151858301516080808a015184516001600160401b0394851681529586018d905292909116928401929092526001600160601b031660608301526001600160a01b038c811692908716917f4267620590840eb55001e83ca966068950e54f161253b9bcb8ad7903d67e2685910160405180910390a387600101975050505050505050610fc7565b5050505050565b600282015460078301545f9190829082906113bd90600160801b90046001600160401b0316866132c6565b6113c791906132c6565b90506113d382826126a9565b925050505b92915050565b5f80603283116113fd575069043c33c193756480000092600192509050565b603c831161141a5750690878678326eac900000092600292509050565b604683116114375750690cb49b44ba602d80000092600492509050565b6050831161145457506910f0cf064dd59200000092600892509050565b5069152d02c7e14af680000092600a92509050565b5f6001600160601b038211156114a0576040516306dfcc6560e41b8152606060048201526024810183905260440160405180910390fd5b5090565b60605f6202a300835f01516001600160601b03166114c291906132c6565b905083516001600160401b038111156114dd576114dd612f83565b604051908082528060200260200182016040528015611506578160200160208202803683370190505b5091505f5b84518110156117f4575f8582815181106115275761152761348c565b6020026020010151905061154481886126d590919063ffffffff16565b1561156257604051636bb8676960e11b815260040160405180910390fd5b5f61156d8883612437565b905061157881612474565b6115955760405163f1a3275d60e01b815260040160405180910390fd5b61159e8161274c565b156115bc576040516353b91a5160e01b815260040160405180910390fd5b6115c588611ed3565b8584815181106115d7576115d761348c565b60200260200101906001600160401b031690816001600160401b0316815250508483815181106116095761160961348c565b6020908102919091018101516001600160401b0390811660e089019081525f85815260048c0184526040908190208a51948b01516001600160a01b03908116600160601b9081026001600160601b03978816178355928c015160608d01518216840290871617600183015560808c015160a08d0151909116909202919094161760028085019190915560c08a01516003850180549351610100808e015163ffffffff166901000000000000000000026cffffffff00000000000000000019929097160268ffffffffffffffff00199315159390931668ffffffffffffffffff199095169490941791909117928316841781556101208b01518b9594909391926dff00000000000000000000000000199091166dffffffffff000000000000000000199092169190911790600160681b90849081111561174a5761174a6132d9565b021790555061014091909101518051805160048401805460209384015161ffff908116600160a01b9081026001600160b01b03199384166001600160a01b0396871617179093559484015180516005909701805491909501519095169091029316931692909217179055805467ffffffffffffffff1963ffffffff8616600160401b02166bffffffffffffffffffffffff19909116176001600160401b031790555060010161150b565b5061182e856203f4806203f47f4201046001600160601b03166203f4806203f47f63ffffffff861601046001600160601b03168751611f16565b935093915050565b811561138b5761184885848484612774565b61138b848484612857565b60605f6202a300835f01516001600160601b031661187191906132c6565b90505f6203f4806203f47f4201046203f4806203f47f63ffffffff8516010461189a919061353d565b6001600160601b0316905060608515611900576118fd6203f4806203f47f4201046001600160601b03166118f26203f4806203f47f8a010460078d01546001600160601b0391909116906001600160401b031661288a565b60018c01919061289f565b90505b86516001600160401b0381111561191957611919612f83565b604051908082528060200260200182016040528015611942578160200160208202803683370190505b5093505f5b8751811015611d5a5761197c8882815181106119655761196561348c565b60200260200101518b6126d590919063ffffffff16565b1561199a57604051636bb8676960e11b815260040160405180910390fd5b5f6119c98a8a84815181106119b1576119b161348c565b60200260200101518d6129d09092919063ffffffff16565b508054909150600160401b900463ffffffff16611a405760078b018054601890611a1790780100000000000000000000000000000000000000000000000090046001600160401b0316613564565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550611b08565b805463ffffffff600160401b9091048116908616811015611a7457604051633494a40d60e21b815260040160405180910390fd5b5f6203f4806203f47f4201046203f4806203f47f840104611a95919061353d565b6001600160601b0316905080861180611aae5750845181115b15611acc57604051633494a40d60e21b815260040160405180910390fd5b855b81811015611b0457858181518110611ae857611ae861348c565b602002602001018051611afa90613585565b9052600101611ace565b5050505b80546bffffffff00000000000000001916600160401b63ffffffff871602178155611b328b611ed3565b868381518110611b4457611b4461348c565b60200260200101906001600160401b031690816001600160401b031681525050858281518110611b7657611b7661348c565b60200260200101518760e001906001600160401b031690816001600160401b031681525050868b6004015f8b8581518110611bb357611bb361348c565b60209081029190910181015182528181019290925260409081015f208351928401516001600160a01b03908116600160601b9081026001600160601b0395861617835592850151606086015182168402908516176001830155608085015160a0860151909116909202919092161760028083019190915560c083015160038301805460e08601516101008088015163ffffffff166901000000000000000000026cffffffff000000000000000000196001600160401b0390931690910268ffffffffffffffff00199515159590951668ffffffffffffffffff199093169290921793909317928316811782556101208601519391926dff00000000000000000000000000199091166dffffffffff000000000000000000199091161790600160681b908490811115611ce757611ce76132d9565b021790555061014091909101518051805160048401805460209384015161ffff908116600160a01b9081026001600160b01b03199384166001600160a01b039687161717909355948401518051600590970180549190950151909516909102931693169290921717905550600101611947565b50805115611d8357611d8360018a016001600160601b036203f480426203f47f01041683612a77565b50509550959350505050565b5f6050821015611da25750610e10919050565b6055821015611db45750612a30919050565b605a821015611dc65750615460919050565b605f821015611dd8575061a8c0919050565b5062015180919050565b919050565b5f8281526003840160205260409020546001600160401b031615611e1e5760405163985816ed60e01b815260040160405180910390fd5b5f8281526003840160209081526040918290208351815492850151938501516001600160a01b0316600160601b026001600160601b0363ffffffff909516600160401b026bffffffffffffffffffffffff199094166001600160401b039283161793909317939093169190911790556007840180549091601091611eaa91600160801b9091041661359a565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b6008810180546001600160401b0316905f611eed8361359a565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550919050565b828214611ff6575f611f29858585612bb5565b80519091505f831315611f7457825f5b82811015611f6d5781848281518110611f5457611f5461348c565b6020908102919091010180519091019052600101611f39565b5050611fb2565b5f838103905b82811015611faf5781848281518110611f9557611f9561348c565b602090810291909101018051919091039052600101611f7a565b50505b50611fc1600186018583612a77565b60078501546001600160401b031683111561138b575060078401805467ffffffffffffffff19166001600160401b0384161790555b50505050565b61201d60405180606001604052805f81526020015f81526020015f81525090565b60048210612029575f80fd5b5f6040518060800160405280604051806060016040528061012c8152602001600181526020015f815250815260200160405180606001604052806170808152602001600a815260200160038152508152602001604051806060016040528061e10081526020016014815260200160058152508152602001604051806060016040528062015180815260200160288152602001600781525081525090508083600481106120d7576120d761348c565b60200201519392505050565b6001600160a01b0381165f9081526004840160205260408120548061210a8560ff16612d09565b1115612129576040516369f131af60e11b815260040160405180910390fd5b90505b9392505050565b80515f908152600483016020526040812082518291908290612156908790612437565b825460028401546001600160601b038082169750600160601b9091046001600160a01b03169550919250165f8190036121a257604051631cacacff60e11b815260040160405180910390fd5b4281116121c25760405163ce2abcbb60e01b815260040160405180910390fd5b85602001516001600160601b0316856fffffffffffffffffffffffffffffffff161015806122045750602086015160018401546001600160601b039182169116115b156122225760405163724f8deb60e01b815260040160405180910390fd5b85604001516001600160a01b0316846001600160a01b03160361225857604051631247b42760e11b815260040160405180910390fd5b5f6003840154600160681b900460ff166002811115612279576122796132d9565b14801561229e5750815460408701516001600160a01b03908116600160601b90920416145b156122bc57604051631247b42760e11b815260040160405180910390fd5b5f856fffffffffffffffffffffffffffffffff16118015612308575061230686602001516001600160601b0316866fffffffffffffffffffffffffffffffff168860800151612dad565b155b1561232657604051636f5353b160e11b815260040160405180910390fd5b815467ffffffffffffffff19166001600160401b0317825560608601515f9061234f90426132c6565b9050818111156123fc575f6123676202a300836132c6565b84549091506123b7908a9061239390600160401b900463ffffffff166203f4806203f47f919091010490565b6001600160601b03166203f4806203f47f8501046001600160601b03166001611f16565b835463ffffffff909116600160401b026bffffffff00000000000000001990911617835583546001600160601b0382166bffffffffffffffffffffffff199091161784555b50505060208401516040909401516001600160a01b0316600160601b026001600160601b039094169390931760029093019290925592909150565b5f8181526003830160205260408120805490916001600160401b0390911690036113d857604051635672499160e01b815260040160405180910390fd5b80545f90600160401b900463ffffffff16158015906113d85750815442600160401b90910463ffffffff161092915050565b602082015161014083015160808401515f90819081906124cf906001600160601b031685612e1e565b305f90815260208c90526040902092955090935091508115612514578451516001600160a01b03165f90815260208a9052604081206125149183919089908690611836565b821561254657602080860151516001600160a01b03165f908152908a9052604081206125469183919089908790611836565b8315612573576001600160a01b0387165f90815260208a9052604081206125739183919089908890611836565b505050505050505050565b600781018054600160401b90046001600160401b0316906008611eed8361359a565b5f82815260208490526040902054600160601b90046001600160401b0316156125dc57604051637f471e3f60e11b815260040160405180910390fd5b6002830180545f906125f39063ffffffff166135d3565b825463ffffffff9182166101009390930a92830291909202199091161790555f91825260209283526040918290208151815494830151939092015160ff16600160a01b027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6001600160401b03909416600160601b027fffffffffffffffffffffffff00000000000000000000000000000000000000009095166001600160601b03909316929092179390931791909116179055565b5f815f036126b857505f6113d8565b8183606402816126ca576126ca6135bf565b0460640390506113d8565b5f818152600483016020526040812054426001600160601b0390911610158061271757505f8281526005840160205260409020544265ffffffffffff90911610155b8061273c57505f828152600684016020526040902054426001600160601b0390911610155b8061212c575061212c8383612e7b565b80545f90429061276d906201518090600160401b900463ffffffff166132c6565b1092915050565b6001600160a01b0383165f908152600485016020526040902054828110156127af57604051638ac4bc7360e01b815260040160405180910390fd5b8115612832578281036127d16127c9876001015460f01c90565b60ff16612d09565b8110806127e7575085546001600160601b031681105b806127f55750856002015481105b1561281357604051638ac4bc7360e01b815260040160405180910390fd5b6001600160a01b0385165f90815260048701602052604090205561138b565b6001600160a01b0384165f908152600486016020526040902083820390555050505050565b6001600160a01b0382165f908152600484016020526040812080548392906128809084906132c6565b9091555050505050565b5f818310612898578161212c565b5090919050565b6060818311156128c25760405163d571362560e01b815260040160405180910390fd5b82820360f08111156128e75760405163d571362560e01b815260040160405180910390fd5b5f816001600160401b0381111561290057612900612f83565b604051908082528060200260200182016040528015612929578160200160208202803683370190505b5090505f808061294260f0895b06600a80820492910690565b5f82815260208c90526040902054919350915062ffffff601883021b895b898110156129c057602086019550818316601885021c868801528160181b9150600184019350600181019050600a84036129bb5761299f60f082612936565b5f82815260208f905260409020549196509450925062ffffff91505b612960565b50949a9950505050505050505050565b5f818152600384016020526040812080549091906001600160401b03168103612a0c57604051635672499160e01b815260040160405180910390fd5b612a1582612474565b15612a3357604051631d80cfbf60e21b815260040160405180910390fd5b505f828152602084905260409020805482546001600160401b03908116600160601b909204161461182e57604051633c3ca9d760e11b815260040160405180910390fd5b805160f0811115612a9b5760405163d571362560e01b815260040160405180910390fd5b612aa6815f19613479565b831115612ac65760405163d571362560e01b815260040160405180910390fd5b5f80612ad360f086612936565b5f8281526020898152604090912054600190960181029592945090925062ffffff601884021b19905b85811015612b9b578681015162ffffff811115612b2c576040516369600fad60e11b815260040160405180910390fd5b80601886021b808486161794505060018501945062ffffff601886021b199250600a8503612b92575f86815260208b81526040909120859055612b759060f09084048b01612936565b5f82815260208d905260409020549197509550935062ffffff1992505b50602001612afc565b50505f928352602096909652506040902093909355505050565b60078301546060906001600160401b03165f81841015612bd757839150612bdc565b508083035b81851015612cb257805f03612c0057612bf960018701868461289f565b9250612d00565b848203612c0d82826132c6565b6001600160401b03811115612c2457612c24612f83565b604051908082528060200260200182016040528015612c4d578160200160208202803683370190505b5093505f612c5f60018901888661289f565b90505f5b82811015612caa57818181518110612c7d57612c7d61348c565b6020026020010151868281518110612c9757612c9761348c565b6020908102919091010152600101612c63565b505050612d00565b612cbc8585613479565b6001600160401b03811115612cd357612cd3612f83565b604051908082528060200260200182016040528015612cfc578160200160208202803683370190505b5092505b50509392505050565b5f60088210612d16575f80fd5b8160018114612d545760028114612d5e5760038114612d695760048114612d745760058114612d7f5760068114612d8a5760078114612d9557612d9d565b6175309150612d9d565b620186a09150612d9d565b620493e09150612d9d565b620f42409150612d9d565b622dc6c09150612d9d565b629896809150612d9d565b6301c9c38091505b50670de0b6b3a764000002919050565b5f806064612dbb84866132ed565b612dc591906135eb565b90506001811015612dd4575060015b612dde81856132c6565b851015612dee575f91505061212c565b5f612dfa6064876135eb565b90506001811015612e09575060015b612e1381876135fe565b159695505050505050565b5f805f61271084602001516020015161ffff1686612e3c91906132ed565b612e4691906135eb565b84516020015190925061271090612e619061ffff16876132ed565b612e6b91906135eb565b9050808286030392509250925092565b5f81815260068301602052604081206002810154600160a01b90046001600160401b031615801590612eb8575060028101546001600160a01b0316155b8015612ed35750612ed1612ecc8585612437565b612474565b155b949350505050565b60408051610160810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101829052906101208201908152602001612f68604080516080810182525f818301818152606083018290528252825180840190935280835260208381019190915290919082015290565b905290565b80356001600160a01b0381168114611de2575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612fa6575f80fd5b813560206001600160401b0380831115612fc257612fc2612f83565b8260051b604051601f19603f83011681018181108482111715612fe757612fe7612f83565b604052938452858101830193838101925087851115613004575f80fd5b83870191505b848210156130235781358352918301919083019061300a565b979650505050505050565b5f805f805f805f80610100898b031215613046575f80fd5b88359750602089013596506040890135955061306460608a01612f6d565b945061307260808a01612f6d565b935060a08901356001600160401b0381111561308c575f80fd5b6130988b828c01612f97565b9350506130a760c08a01612f6d565b915060e089013590509295985092959890939650565b5f805f805f805f805f6101208a8c0312156130d6575f80fd5b8935985060208a0135975060408a013596506130f460608b01612f6d565b955061310260808b01612f6d565b945060a08a01356001600160401b0381111561311c575f80fd5b6131288c828d01612f97565b94505060c08a0135925061313e60e08b01612f6d565b91506101008a013590509295985092959850929598565b5f805f805f805f80610100898b03121561316d575f80fd5b88359750602089013596506040890135955061318b60608a01612f6d565b945061319960808a01612f6d565b935060a08901356001600160401b038111156131b3575f80fd5b6131bf8b828c01612f97565b9350506131ce60c08a01612f6d565b915060e08901356001600160601b03811681146131e9575f80fd5b809150509295985092959890939650565b5f805f805f805f60e0888a031215613210575f80fd5b873596506020880135955061322760408901612f6d565b945061323560608901612f6d565b9699959850939660808101359560a0820135955060c0909101359350915050565b5f805f8060808587031215613269575f80fd5b843593506020850135925061328060408601612f6d565b915060608501356001600160401b0381111561329a575f80fd5b6132a687828801612f97565b91505092959194509250565b634e487b7160e01b5f52601160045260245ffd5b808201808211156113d8576113d86132b2565b634e487b7160e01b5f52602160045260245ffd5b80820281158282048414176113d8576113d86132b2565b5f8151808452602080850194508084015f5b8381101561333b5781516001600160401b031687529582019590820190600101613316565b509495945050505050565b5f8151808452602080850194508084015f5b8381101561333b57815187529582019590820190600101613358565b6003811061339057634e487b7160e01b5f52602160045260245ffd5b9052565b6133b682825180516001600160a01b0316825260209081015161ffff16910152565b60209081015180516001600160a01b03166040840152015161ffff16606090910152565b5f6101808083526133ed8184018d613304565b90508281036020840152613401818c613346565b915050613411604083018a613374565b61341e6060830189613394565b6001600160a01b039690961660e08201526101008101949094526001600160601b039290921661012084015277ffffffffffffffffffffffffffffffffffffffffffffffff1661014083015261016090910152949350505050565b818103818111156113d8576113d86132b2565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b5f6101808083526134c78184018d613304565b905082810360208401526134db818c613346565b9150506134eb604083018a613374565b6134f86060830189613394565b6001600160a01b039690961660e08201526001600160601b039485166101008201529290931661012083015263ffffffff166101408201526101600152949350505050565b6001600160601b0382811682821603908082111561355d5761355d6132b2565b5092915050565b5f6001600160401b0382168061357c5761357c6132b2565b5f190192915050565b5f81613593576135936132b2565b505f190190565b5f6001600160401b038083168181036135b5576135b56132b2565b6001019392505050565b634e487b7160e01b5f52601260045260245ffd5b5f63ffffffff8083168181036135b5576135b56132b2565b5f826135f9576135f96135bf565b500490565b5f8261360c5761360c6135bf565b50069056fea164736f6c6343000814000a
Deployed Bytecode
0x732f0af2d3e8ba7b3d77c8128dd00a3b3894d95f46301460806040526004361061006f575f3560e01c80631c1a270f116100585780631c1a270f146100b35780635b20b5c6146100d2578063ba56df4e146100f1575f80fd5b806303e4298f146100735780630ffcb2ba14610094575b5f80fd5b81801561007e575f80fd5b5061009261008d36600461302e565b610110565b005b81801561009f575f80fd5b506100926100ae3660046130bd565b6103d7565b8180156100be575f80fd5b506100926100cd366004613155565b6105b0565b8180156100dd575f80fd5b506100926100ec3660046131fa565b610cc9565b8180156100fc575f80fd5b5061009261010b366004613256565b610fc5565b60018601546001600160a01b031661013b57604051633dd1b30560e01b815260040160405180910390fd5b5f610146895f611392565b90505f610152826113de565b5090508083101561017657604051636f5353b160e11b815260040160405180910390fd5b5050610180612edb565b61018d62015180426132c6565b6001600160601b031681526001600160a01b03831660208201526101b082611469565b6001600160601b031660408201523360608201526101cd82611469565b6001600160601b031660808201523360a08201526001610120820181905250604080516080810182525f818301818152606083018290528252825180840190935260018a01546001600160a01b0381168452600160b01b900461ffff166020848101919091528201929092526101408301528061024b8b87856114a4565b63ffffffff16915091505f86515f61026391906132ed565b9050886001600160a01b0316866001600160a01b0316036102e557305f90815260208c90526040902087516102e09190889084906102a1908a6132ed565b6102ab91906132c6565b60018f5f336001600160a01b03166001600160a01b031681526020019081526020015f2061183690949392919063ffffffff16565b610361565b305f90815260208c905260409020875161033a9190889061030690896132ed565b5f8f5f336001600160a01b03166001600160a01b031681526020019081526020015f2061183690949392919063ffffffff16565b801561036157305f90815260208c905260408082203383529120610361918b846001611836565b876001600160a01b0316336001600160a01b03167fd6633dc7eec5401de6894895436c2b403611ed125cdde2fa99e0b362ef9bef8c858a8861012001518961014001518c8c8c5f01518b8b6040516103c1999897969594939291906133da565b60405180910390a3505050505050505050505050565b60018701546001600160a01b031661040257604051633dd1b30560e01b815260040160405180910390fd5b335f90815260208990526040812085519091906104289068056bc75e2d631000006132ed565b305f90815260208c9052604090209091506104489083908a846001611836565b610450612edb565b61045d62015180426132c6565b6001600160601b031681526001600160a01b038516602082015261048084611469565b6001600160601b031660408201523360608201525f6080820181905260a08201819052610120820181905250604080516080810182525f818301818152606083018290528252825180840190935260018d01546001600160a01b0381168452600160a01b900461ffff166020848101919091528201929092526101408301528061052e8e610526878d6001600160a01b03165f9081526003919091016020526040902090565b8b8b87611853565b63ffffffff1691509150896001600160a01b0316336001600160a01b03167fd6633dc7eec5401de6894895436c2b403611ed125cdde2fa99e0b362ef9bef8c848c8761012001518861014001518d8d8b5f01518a8e604051610598999897969594939291906133da565b60405180910390a35050505050505050505050505050565b82516001146105d257604051633494a40d60e21b815260040160405180910390fd5b60028601546001600160a01b03166105fd57604051633dd1b30560e01b815260040160405180910390fd5b5f610608895f611392565b90505f61061482611d8f565b60088b0154909150819061063590600160401b900463ffffffff1642613479565b116106535760405163d6516b8960e01b815260040160405180910390fd5b5f61065d836113de565b50905080846001600160601b0316101561068a57604051636f5353b160e11b815260040160405180910390fd5b505050610695612edb565b6106a262015180426132c6565b6001600160601b0390811682526001600160a01b03848116602080850191909152848316604080860182905233606080880182905260808089019490945260a088019190915260026101208801819052825193840183528d5480871685850190815261ffff600160b01b909204821686850152855283518085018552918f01549687168252600160a01b9096049095168585015282840194909452610140860191909152805192830190526001600160401b03825283515f939183019161076e916202a30091166132c6565b63ffffffff168152602001306001600160a01b031681525090505f85516001600160401b038111156107a2576107a2612f83565b6040519080825280602002602001820160405280156107cb578160200160208202803683370190505b5086519091505b8015610b8f5760028c015487515f19909201918890839081106107f7576107f761348c565b60200260200101511061081d57604051633494a40d60e21b815260040160405180910390fd5b5f8c6002018883815181106108345761083461348c565b60200260200101518154811061084c5761084c61348c565b5f9182526020909120015490506108648d8286611de7565b61086d8d611ed3565b8560e001906001600160401b031690816001600160401b031681525050848d6004015f8381526020019081526020015f205f820151815f015f6101000a8154816001600160601b0302191690836001600160601b031602179055506020820151815f01600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816001015f6101000a8154816001600160601b0302191690836001600160601b03160217905550606082015181600101600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506080820151816002015f6101000a8154816001600160601b0302191690836001600160601b0316021790555060a082015181600201600c6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060c0820151816003015f6101000a81548160ff02191690831515021790555060e08201518160030160016101000a8154816001600160401b0302191690836001600160401b031602179055506101008201518160030160096101000a81548163ffffffff021916908363ffffffff16021790555061012082015181600301600d6101000a81548160ff02191690836002811115610a4557610a456132d9565b021790555061014091909101518051805160048401805460209384015161ffff908116600160a01b9081026001600160b01b03199384166001600160a01b039687161717909355948401518051600590970180549190950151909516909102931693169290921717905560e08501518351849084908110610ac857610ac861348c565b6001600160401b039092166020928302919091019091015260028d018054610af290600190613479565b81548110610b0257610b0261348c565b905f5260205f2001548d600201898481518110610b2157610b2161348c565b602002602001015181548110610b3957610b3961348c565b5f9182526020909120015560028d01805480610b5757610b576134a0565b600190038181905f5260205f20015f9055905580888381518110610b7d57610b7d61348c565b602002602001018181525050506107d2565b50305f90815260208b81526040909120908401518751610be3929190610bbe906001600160601b0389166132ed565b335f90815260208f9052604090209291906001600160a01b038a8116908e1614611836565b610c298b6203f4806203f47f4201046001600160601b0316610c19856020015163ffffffff166203f4806203f47f919091010490565b6001600160601b03168951611f16565b428b60080160086101000a81548163ffffffff021916908363ffffffff160217905550866001600160a01b0316336001600160a01b03167fd6633dc7eec5401de6894895436c2b403611ed125cdde2fa99e0b362ef9bef8c838987610120015188610140015189602001518b8b5f01518b602001515f604051610cb4999897969594939291906134b4565b60405180910390a35050505050505050505050565b5f805f610cd584611ffc565b604080820151335f90815260208d905291909120919250610cf791908a6120e3565b50610d408a6040518060a00160405280898152602001610d1689611469565b6001600160601b031681523360208083019190915285516040830152850151606090910152612133565b5f88815260048d016020908152604080832081516101608101835281546001600160601b038082168352600160601b918290046001600160a01b039081169684019690965260018401548082169584019590955293819004851660608301526002808401549485166080840152930490931660a0840152600381015460ff808216151560c08601526101008083046001600160401b031660e08701526901000000000000000000830463ffffffff16908601526fffffffffffffffffffffffffffffffff97909716995094975092955090939192610120850192600160681b9091041690811115610e3357610e336132d9565b6002811115610e4457610e446132d9565b81526040805160808101825260048401546001600160a01b0380821683850190815261ffff600160a01b938490048116606086015290845284518086018652600590970154808316885292909204909116602086810191909152828101959095529284015283830151305f908152938e9052818420338552919093209394509192610edb92909184908a90828116908f1614611836565b8315610f0f576001600160a01b0383165f90815260208b90526040808220308352908220610f0f9290919084908890611836565b5f87815260038c01602090815260409182902082516060808201855291546001600160401b03808216835263ffffffff600160401b8304168386018190526001600160a01b03600160601b90930483168488015260e0890151895188519190931681529586018e90529585018c90526001600160601b03169284019290925260808301939093528a169033907f803010fdbef76f36b5d3e5ac091de314dede9c52855c46fd5e8c0b040961a40c9060a0016103c1565b5f5b815181101561138b575f828281518110610fe357610fe361348c565b602002602001015190505f610ff88783612437565b905061100381612474565b1561102157604051631d80cfbf60e21b815260040160405180910390fd5b5f828152600488016020908152604080832081516101608101835281546001600160601b038082168352600160601b918290046001600160a01b039081169684019690965260018401548082169584019590955293819004851660608301526002808401549485166080840152930490931660a0840152600381015460ff808216151560c08601526101008083046001600160401b031660e08701526901000000000000000000830463ffffffff16908601529192610120850192600160681b909204909116908111156110f7576110f76132d9565b6002811115611108576111086132d9565b81526040805160808101825260048401546001600160a01b0380821683850190815261ffff600160a01b9384900481166060860152908452845180860190955260059096015490811684520490931660208281019190915283810191909152015280519091506001600160601b03165f0361119657604051631cacacff60e11b815260040160405180910390fd5b42815f01516001600160601b031611156111c357604051630a0619fd60e01b815260040160405180910390fd5b60a08101516001600160a01b03166111ee57604051630a0619fd60e01b815260040160405180910390fd5b815461120d9088908390600160601b90046001600160a01b03166124a6565b60a081015160408051606081019091525f808252906020810161122f8c61257e565b6001600160401b0390811682525f602092830181905283830151885492166bffffffff000000000000000090921691909117600160601b6001600160a01b0387811691820292909217895582528c83526040808320918d16835260038201909352919020919250906112a28188856125a0565b5f87815260048d8101602090815260408084208481556001810185905560028101949094556003840180546dffffffffffffffffffffffffffff1916905591830180546001600160b01b03199081169091556005909301805490931690925560e0870151858301516080808a015184516001600160401b0394851681529586018d905292909116928401929092526001600160601b031660608301526001600160a01b038c811692908716917f4267620590840eb55001e83ca966068950e54f161253b9bcb8ad7903d67e2685910160405180910390a387600101975050505050505050610fc7565b5050505050565b600282015460078301545f9190829082906113bd90600160801b90046001600160401b0316866132c6565b6113c791906132c6565b90506113d382826126a9565b925050505b92915050565b5f80603283116113fd575069043c33c193756480000092600192509050565b603c831161141a5750690878678326eac900000092600292509050565b604683116114375750690cb49b44ba602d80000092600492509050565b6050831161145457506910f0cf064dd59200000092600892509050565b5069152d02c7e14af680000092600a92509050565b5f6001600160601b038211156114a0576040516306dfcc6560e41b8152606060048201526024810183905260440160405180910390fd5b5090565b60605f6202a300835f01516001600160601b03166114c291906132c6565b905083516001600160401b038111156114dd576114dd612f83565b604051908082528060200260200182016040528015611506578160200160208202803683370190505b5091505f5b84518110156117f4575f8582815181106115275761152761348c565b6020026020010151905061154481886126d590919063ffffffff16565b1561156257604051636bb8676960e11b815260040160405180910390fd5b5f61156d8883612437565b905061157881612474565b6115955760405163f1a3275d60e01b815260040160405180910390fd5b61159e8161274c565b156115bc576040516353b91a5160e01b815260040160405180910390fd5b6115c588611ed3565b8584815181106115d7576115d761348c565b60200260200101906001600160401b031690816001600160401b0316815250508483815181106116095761160961348c565b6020908102919091018101516001600160401b0390811660e089019081525f85815260048c0184526040908190208a51948b01516001600160a01b03908116600160601b9081026001600160601b03978816178355928c015160608d01518216840290871617600183015560808c015160a08d0151909116909202919094161760028085019190915560c08a01516003850180549351610100808e015163ffffffff166901000000000000000000026cffffffff00000000000000000019929097160268ffffffffffffffff00199315159390931668ffffffffffffffffff199095169490941791909117928316841781556101208b01518b9594909391926dff00000000000000000000000000199091166dffffffffff000000000000000000199092169190911790600160681b90849081111561174a5761174a6132d9565b021790555061014091909101518051805160048401805460209384015161ffff908116600160a01b9081026001600160b01b03199384166001600160a01b0396871617179093559484015180516005909701805491909501519095169091029316931692909217179055805467ffffffffffffffff1963ffffffff8616600160401b02166bffffffffffffffffffffffff19909116176001600160401b031790555060010161150b565b5061182e856203f4806203f47f4201046001600160601b03166203f4806203f47f63ffffffff861601046001600160601b03168751611f16565b935093915050565b811561138b5761184885848484612774565b61138b848484612857565b60605f6202a300835f01516001600160601b031661187191906132c6565b90505f6203f4806203f47f4201046203f4806203f47f63ffffffff8516010461189a919061353d565b6001600160601b0316905060608515611900576118fd6203f4806203f47f4201046001600160601b03166118f26203f4806203f47f8a010460078d01546001600160601b0391909116906001600160401b031661288a565b60018c01919061289f565b90505b86516001600160401b0381111561191957611919612f83565b604051908082528060200260200182016040528015611942578160200160208202803683370190505b5093505f5b8751811015611d5a5761197c8882815181106119655761196561348c565b60200260200101518b6126d590919063ffffffff16565b1561199a57604051636bb8676960e11b815260040160405180910390fd5b5f6119c98a8a84815181106119b1576119b161348c565b60200260200101518d6129d09092919063ffffffff16565b508054909150600160401b900463ffffffff16611a405760078b018054601890611a1790780100000000000000000000000000000000000000000000000090046001600160401b0316613564565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550611b08565b805463ffffffff600160401b9091048116908616811015611a7457604051633494a40d60e21b815260040160405180910390fd5b5f6203f4806203f47f4201046203f4806203f47f840104611a95919061353d565b6001600160601b0316905080861180611aae5750845181115b15611acc57604051633494a40d60e21b815260040160405180910390fd5b855b81811015611b0457858181518110611ae857611ae861348c565b602002602001018051611afa90613585565b9052600101611ace565b5050505b80546bffffffff00000000000000001916600160401b63ffffffff871602178155611b328b611ed3565b868381518110611b4457611b4461348c565b60200260200101906001600160401b031690816001600160401b031681525050858281518110611b7657611b7661348c565b60200260200101518760e001906001600160401b031690816001600160401b031681525050868b6004015f8b8581518110611bb357611bb361348c565b60209081029190910181015182528181019290925260409081015f208351928401516001600160a01b03908116600160601b9081026001600160601b0395861617835592850151606086015182168402908516176001830155608085015160a0860151909116909202919092161760028083019190915560c083015160038301805460e08601516101008088015163ffffffff166901000000000000000000026cffffffff000000000000000000196001600160401b0390931690910268ffffffffffffffff00199515159590951668ffffffffffffffffff199093169290921793909317928316811782556101208601519391926dff00000000000000000000000000199091166dffffffffff000000000000000000199091161790600160681b908490811115611ce757611ce76132d9565b021790555061014091909101518051805160048401805460209384015161ffff908116600160a01b9081026001600160b01b03199384166001600160a01b039687161717909355948401518051600590970180549190950151909516909102931693169290921717905550600101611947565b50805115611d8357611d8360018a016001600160601b036203f480426203f47f01041683612a77565b50509550959350505050565b5f6050821015611da25750610e10919050565b6055821015611db45750612a30919050565b605a821015611dc65750615460919050565b605f821015611dd8575061a8c0919050565b5062015180919050565b919050565b5f8281526003840160205260409020546001600160401b031615611e1e5760405163985816ed60e01b815260040160405180910390fd5b5f8281526003840160209081526040918290208351815492850151938501516001600160a01b0316600160601b026001600160601b0363ffffffff909516600160401b026bffffffffffffffffffffffff199094166001600160401b039283161793909317939093169190911790556007840180549091601091611eaa91600160801b9091041661359a565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b6008810180546001600160401b0316905f611eed8361359a565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550919050565b828214611ff6575f611f29858585612bb5565b80519091505f831315611f7457825f5b82811015611f6d5781848281518110611f5457611f5461348c565b6020908102919091010180519091019052600101611f39565b5050611fb2565b5f838103905b82811015611faf5781848281518110611f9557611f9561348c565b602090810291909101018051919091039052600101611f7a565b50505b50611fc1600186018583612a77565b60078501546001600160401b031683111561138b575060078401805467ffffffffffffffff19166001600160401b0384161790555b50505050565b61201d60405180606001604052805f81526020015f81526020015f81525090565b60048210612029575f80fd5b5f6040518060800160405280604051806060016040528061012c8152602001600181526020015f815250815260200160405180606001604052806170808152602001600a815260200160038152508152602001604051806060016040528061e10081526020016014815260200160058152508152602001604051806060016040528062015180815260200160288152602001600781525081525090508083600481106120d7576120d761348c565b60200201519392505050565b6001600160a01b0381165f9081526004840160205260408120548061210a8560ff16612d09565b1115612129576040516369f131af60e11b815260040160405180910390fd5b90505b9392505050565b80515f908152600483016020526040812082518291908290612156908790612437565b825460028401546001600160601b038082169750600160601b9091046001600160a01b03169550919250165f8190036121a257604051631cacacff60e11b815260040160405180910390fd5b4281116121c25760405163ce2abcbb60e01b815260040160405180910390fd5b85602001516001600160601b0316856fffffffffffffffffffffffffffffffff161015806122045750602086015160018401546001600160601b039182169116115b156122225760405163724f8deb60e01b815260040160405180910390fd5b85604001516001600160a01b0316846001600160a01b03160361225857604051631247b42760e11b815260040160405180910390fd5b5f6003840154600160681b900460ff166002811115612279576122796132d9565b14801561229e5750815460408701516001600160a01b03908116600160601b90920416145b156122bc57604051631247b42760e11b815260040160405180910390fd5b5f856fffffffffffffffffffffffffffffffff16118015612308575061230686602001516001600160601b0316866fffffffffffffffffffffffffffffffff168860800151612dad565b155b1561232657604051636f5353b160e11b815260040160405180910390fd5b815467ffffffffffffffff19166001600160401b0317825560608601515f9061234f90426132c6565b9050818111156123fc575f6123676202a300836132c6565b84549091506123b7908a9061239390600160401b900463ffffffff166203f4806203f47f919091010490565b6001600160601b03166203f4806203f47f8501046001600160601b03166001611f16565b835463ffffffff909116600160401b026bffffffff00000000000000001990911617835583546001600160601b0382166bffffffffffffffffffffffff199091161784555b50505060208401516040909401516001600160a01b0316600160601b026001600160601b039094169390931760029093019290925592909150565b5f8181526003830160205260408120805490916001600160401b0390911690036113d857604051635672499160e01b815260040160405180910390fd5b80545f90600160401b900463ffffffff16158015906113d85750815442600160401b90910463ffffffff161092915050565b602082015161014083015160808401515f90819081906124cf906001600160601b031685612e1e565b305f90815260208c90526040902092955090935091508115612514578451516001600160a01b03165f90815260208a9052604081206125149183919089908690611836565b821561254657602080860151516001600160a01b03165f908152908a9052604081206125469183919089908790611836565b8315612573576001600160a01b0387165f90815260208a9052604081206125739183919089908890611836565b505050505050505050565b600781018054600160401b90046001600160401b0316906008611eed8361359a565b5f82815260208490526040902054600160601b90046001600160401b0316156125dc57604051637f471e3f60e11b815260040160405180910390fd5b6002830180545f906125f39063ffffffff166135d3565b825463ffffffff9182166101009390930a92830291909202199091161790555f91825260209283526040918290208151815494830151939092015160ff16600160a01b027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6001600160401b03909416600160601b027fffffffffffffffffffffffff00000000000000000000000000000000000000009095166001600160601b03909316929092179390931791909116179055565b5f815f036126b857505f6113d8565b8183606402816126ca576126ca6135bf565b0460640390506113d8565b5f818152600483016020526040812054426001600160601b0390911610158061271757505f8281526005840160205260409020544265ffffffffffff90911610155b8061273c57505f828152600684016020526040902054426001600160601b0390911610155b8061212c575061212c8383612e7b565b80545f90429061276d906201518090600160401b900463ffffffff166132c6565b1092915050565b6001600160a01b0383165f908152600485016020526040902054828110156127af57604051638ac4bc7360e01b815260040160405180910390fd5b8115612832578281036127d16127c9876001015460f01c90565b60ff16612d09565b8110806127e7575085546001600160601b031681105b806127f55750856002015481105b1561281357604051638ac4bc7360e01b815260040160405180910390fd5b6001600160a01b0385165f90815260048701602052604090205561138b565b6001600160a01b0384165f908152600486016020526040902083820390555050505050565b6001600160a01b0382165f908152600484016020526040812080548392906128809084906132c6565b9091555050505050565b5f818310612898578161212c565b5090919050565b6060818311156128c25760405163d571362560e01b815260040160405180910390fd5b82820360f08111156128e75760405163d571362560e01b815260040160405180910390fd5b5f816001600160401b0381111561290057612900612f83565b604051908082528060200260200182016040528015612929578160200160208202803683370190505b5090505f808061294260f0895b06600a80820492910690565b5f82815260208c90526040902054919350915062ffffff601883021b895b898110156129c057602086019550818316601885021c868801528160181b9150600184019350600181019050600a84036129bb5761299f60f082612936565b5f82815260208f905260409020549196509450925062ffffff91505b612960565b50949a9950505050505050505050565b5f818152600384016020526040812080549091906001600160401b03168103612a0c57604051635672499160e01b815260040160405180910390fd5b612a1582612474565b15612a3357604051631d80cfbf60e21b815260040160405180910390fd5b505f828152602084905260409020805482546001600160401b03908116600160601b909204161461182e57604051633c3ca9d760e11b815260040160405180910390fd5b805160f0811115612a9b5760405163d571362560e01b815260040160405180910390fd5b612aa6815f19613479565b831115612ac65760405163d571362560e01b815260040160405180910390fd5b5f80612ad360f086612936565b5f8281526020898152604090912054600190960181029592945090925062ffffff601884021b19905b85811015612b9b578681015162ffffff811115612b2c576040516369600fad60e11b815260040160405180910390fd5b80601886021b808486161794505060018501945062ffffff601886021b199250600a8503612b92575f86815260208b81526040909120859055612b759060f09084048b01612936565b5f82815260208d905260409020549197509550935062ffffff1992505b50602001612afc565b50505f928352602096909652506040902093909355505050565b60078301546060906001600160401b03165f81841015612bd757839150612bdc565b508083035b81851015612cb257805f03612c0057612bf960018701868461289f565b9250612d00565b848203612c0d82826132c6565b6001600160401b03811115612c2457612c24612f83565b604051908082528060200260200182016040528015612c4d578160200160208202803683370190505b5093505f612c5f60018901888661289f565b90505f5b82811015612caa57818181518110612c7d57612c7d61348c565b6020026020010151868281518110612c9757612c9761348c565b6020908102919091010152600101612c63565b505050612d00565b612cbc8585613479565b6001600160401b03811115612cd357612cd3612f83565b604051908082528060200260200182016040528015612cfc578160200160208202803683370190505b5092505b50509392505050565b5f60088210612d16575f80fd5b8160018114612d545760028114612d5e5760038114612d695760048114612d745760058114612d7f5760068114612d8a5760078114612d9557612d9d565b6175309150612d9d565b620186a09150612d9d565b620493e09150612d9d565b620f42409150612d9d565b622dc6c09150612d9d565b629896809150612d9d565b6301c9c38091505b50670de0b6b3a764000002919050565b5f806064612dbb84866132ed565b612dc591906135eb565b90506001811015612dd4575060015b612dde81856132c6565b851015612dee575f91505061212c565b5f612dfa6064876135eb565b90506001811015612e09575060015b612e1381876135fe565b159695505050505050565b5f805f61271084602001516020015161ffff1686612e3c91906132ed565b612e4691906135eb565b84516020015190925061271090612e619061ffff16876132ed565b612e6b91906135eb565b9050808286030392509250925092565b5f81815260068301602052604081206002810154600160a01b90046001600160401b031615801590612eb8575060028101546001600160a01b0316155b8015612ed35750612ed1612ecc8585612437565b612474565b155b949350505050565b60408051610160810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101829052906101208201908152602001612f68604080516080810182525f818301818152606083018290528252825180840190935280835260208381019190915290919082015290565b905290565b80356001600160a01b0381168114611de2575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612fa6575f80fd5b813560206001600160401b0380831115612fc257612fc2612f83565b8260051b604051601f19603f83011681018181108482111715612fe757612fe7612f83565b604052938452858101830193838101925087851115613004575f80fd5b83870191505b848210156130235781358352918301919083019061300a565b979650505050505050565b5f805f805f805f80610100898b031215613046575f80fd5b88359750602089013596506040890135955061306460608a01612f6d565b945061307260808a01612f6d565b935060a08901356001600160401b0381111561308c575f80fd5b6130988b828c01612f97565b9350506130a760c08a01612f6d565b915060e089013590509295985092959890939650565b5f805f805f805f805f6101208a8c0312156130d6575f80fd5b8935985060208a0135975060408a013596506130f460608b01612f6d565b955061310260808b01612f6d565b945060a08a01356001600160401b0381111561311c575f80fd5b6131288c828d01612f97565b94505060c08a0135925061313e60e08b01612f6d565b91506101008a013590509295985092959850929598565b5f805f805f805f80610100898b03121561316d575f80fd5b88359750602089013596506040890135955061318b60608a01612f6d565b945061319960808a01612f6d565b935060a08901356001600160401b038111156131b3575f80fd5b6131bf8b828c01612f97565b9350506131ce60c08a01612f6d565b915060e08901356001600160601b03811681146131e9575f80fd5b809150509295985092959890939650565b5f805f805f805f60e0888a031215613210575f80fd5b873596506020880135955061322760408901612f6d565b945061323560608901612f6d565b9699959850939660808101359560a0820135955060c0909101359350915050565b5f805f8060808587031215613269575f80fd5b843593506020850135925061328060408601612f6d565b915060608501356001600160401b0381111561329a575f80fd5b6132a687828801612f97565b91505092959194509250565b634e487b7160e01b5f52601160045260245ffd5b808201808211156113d8576113d86132b2565b634e487b7160e01b5f52602160045260245ffd5b80820281158282048414176113d8576113d86132b2565b5f8151808452602080850194508084015f5b8381101561333b5781516001600160401b031687529582019590820190600101613316565b509495945050505050565b5f8151808452602080850194508084015f5b8381101561333b57815187529582019590820190600101613358565b6003811061339057634e487b7160e01b5f52602160045260245ffd5b9052565b6133b682825180516001600160a01b0316825260209081015161ffff16910152565b60209081015180516001600160a01b03166040840152015161ffff16606090910152565b5f6101808083526133ed8184018d613304565b90508281036020840152613401818c613346565b915050613411604083018a613374565b61341e6060830189613394565b6001600160a01b039690961660e08201526101008101949094526001600160601b039290921661012084015277ffffffffffffffffffffffffffffffffffffffffffffffff1661014083015261016090910152949350505050565b818103818111156113d8576113d86132b2565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b5f6101808083526134c78184018d613304565b905082810360208401526134db818c613346565b9150506134eb604083018a613374565b6134f86060830189613394565b6001600160a01b039690961660e08201526001600160601b039485166101008201529290931661012083015263ffffffff166101408201526101600152949350505050565b6001600160601b0382811682821603908082111561355d5761355d6132b2565b5092915050565b5f6001600160401b0382168061357c5761357c6132b2565b5f190192915050565b5f81613593576135936132b2565b505f190190565b5f6001600160401b038083168181036135b5576135b56132b2565b6001019392505050565b634e487b7160e01b5f52601260045260245ffd5b5f63ffffffff8083168181036135b5576135b56132b2565b5f826135f9576135f96135bf565b500490565b5f8261360c5761360c6135bf565b50069056fea164736f6c6343000814000a
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
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.