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/contracts/utils/math/SafeCast.sol"; import {SafeBox, CollectionState, AuctionInfo} 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 AuctionStarted( address indexed trigger, address indexed collection, uint64[] activityIds, uint256[] tokenIds, address settleToken, uint256 minimumBid, uint256 feeRateBips, uint256 auctionEndTime, uint256 safeBoxExpiryTs, bool selfTriggered, 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, address creditToken, address collectionId, uint256[] memory nftIds, uint256 maxExpiry, address token, uint256 minimumBid ) public { 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.isSelfTriggered = true; auctionTemplate.feeRateBips = uint32(getAuctionFeeRate(true, creditToken, address(collection.floorToken), token)); auctionTemplate.lastBidAmount = 0; auctionTemplate.lastBidder = address(0); (uint64[] memory activityIds, uint192 newExpiryTs) = _ownerInitAuctions(collection, userAccount.getByKey(collectionId), nftIds, maxExpiry, auctionTemplate); emit AuctionStarted( msg.sender, collectionId, activityIds, nftIds, token, minimumBid, auctionTemplate.feeRateBips, auctionTemplate.endTime, newExpiryTs, true, 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, address creditToken, address collectionId, uint256[] memory nftIds, address bidToken, uint256 bidAmount ) public { if (bidAmount < Constants.AUCTION_ON_EXPIRED_MINIMUM_BID) revert Errors.InvalidParam(); AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = bidToken; auctionTemplate.minimumBid = bidAmount.toUint96(); auctionTemplate.triggerAddress = msg.sender; auctionTemplate.isSelfTriggered = false; auctionTemplate.feeRateBips = uint32(getAuctionFeeRate(false, creditToken, address(collection.floorToken), bidToken)); auctionTemplate.lastBidAmount = bidAmount.toUint96(); auctionTemplate.lastBidder = msg.sender; (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 AuctionStarted( msg.sender, collectionId, activityIds, nftIds, bidToken, bidAmount, auctionTemplate.feeRateBips, auctionTemplate.endTime, newExpiry, false, 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; } } applyDiffToCounters( collectionState, Helper.counterStamp(block.timestamp), Helper.counterStamp(newExpiry), int256(nftIds.length) ); } function initAuctionOnVault( CollectionState storage collection, mapping(address => UserFloorAccount) storage userAccounts, address creditToken, address collectionId, uint256[] memory vaultIdx, address bidToken, uint96 bidAmount ) public { if (vaultIdx.length != 1) revert Errors.InvalidParam(); if (bidAmount < Constants.AUCTION_ON_VAULT_MINIMUM_BID) revert Errors.InvalidParam(); { /// check auction period uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0); uint256 periodDuration = Constants.getVaultAuctionDurationAtLR(lockingRatio); if (block.timestamp - collection.lastVaultAuctionPeriodTs <= periodDuration) { revert Errors.PeriodQuotaExhausted(); } } AuctionInfo memory auctionTemplate; auctionTemplate.endTime = uint96(block.timestamp + Constants.AUCTION_INITIAL_PERIODS); auctionTemplate.bidTokenAddress = bidToken; auctionTemplate.minimumBid = bidAmount; auctionTemplate.triggerAddress = msg.sender; auctionTemplate.isSelfTriggered = false; auctionTemplate.feeRateBips = 0; auctionTemplate.lastBidAmount = bidAmount; auctionTemplate.lastBidder = msg.sender; SafeBox memory safeboxTemplate = SafeBox({ keyId: SafeBoxLib.SAFEBOX_KEY_NOTATION, expiryTs: uint32(auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS), owner: address(this) }); uint256[] memory nftIds = new uint256[](vaultIdx.length); 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]]; nftIds[i] = nftId; 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(); } userAccounts[msg.sender].transferToken( userAccounts[address(this)], auctionTemplate.bidTokenAddress, bidAmount * nftIds.length, bidToken == creditToken ); applyDiffToCounters( collection, Helper.counterStamp(block.timestamp), Helper.counterStamp(safeboxTemplate.expiryTs), int256(nftIds.length) ); /// update auction timestamp collection.lastVaultAuctionPeriodTs = uint32(block.timestamp); emit AuctionStarted( msg.sender, collectionId, activityIds, nftIds, auctionTemplate.bidTokenAddress, bidAmount, auctionTemplate.feeRateBips, auctionTemplate.endTime, safeboxTemplate.expiryTs, false, 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.isSelfTriggered && 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; 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(); (uint256 earning,) = Helper.calculateActivityFee(auctionInfo.lastBidAmount, auctionInfo.feeRateBips); /// contract account no need to check credit requirements /// transfer earnings to old safebox owner userAccounts[address(this)].transferToken( userAccounts[safebox.owner], auctionInfo.bidTokenAddress, earning, false ); /// 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 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; } function applyDiffToCounters( CollectionState storage collectionState, uint256 startBucket, uint256 endBucket, int256 diff ) private { 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 getAuctionFeeRate(bool isSelfTriggered, address creditToken, address floorToken, address settleToken) private pure returns (uint256) { if (isSelfTriggered) { /// owner self trigger the aution return Helper.getTokenFeeRateBips(creditToken, floorToken, settleToken); } else { return Constants.FREE_AUCTION_FEE_RATE_BIPS; } } }
// 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/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 minimum bid per NFT when someone starts aution on expired safebox uint256 public constant AUCTION_ON_EXPIRED_MINIMUM_BID = 50000 ether; /// @dev minimum bid per NFT when someone starts aution on vault uint256 public constant AUCTION_ON_VAULT_MINIMUM_BID = 50000 ether; /// @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; uint256 public constant VAULT_REDEMPTION_MAX_LOKING_RATIO = 80; /// @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% /// @notice Fee rate settled with credit token uint256 public constant CREDIT_FEE_RATE_BIPS = 150; // 2% /// @notice Fee rate settled with specified token uint256 public constant SPEC_FEE_RATE_BIPS = 300; // 3% /// @notice Fee rate settled with all other tokens uint256 public constant COMMON_FEE_RATE_BIPS = 500; // 5% 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 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; } } } /// @dev returns (costAfterDiscount, quotaUsedAfter) function getVipClaimCostWithDiscount(uint256 cost, uint8 vipLevel, uint96 quotaUsed) internal pure returns (uint256, uint96) { uint96 totalQuota = 1 ether; assembly { switch vipLevel case 0 { totalQuota := mul(0, totalQuota) } case 1 { totalQuota := mul(2000, totalQuota) } case 2 { totalQuota := mul(4000, totalQuota) } case 3 { totalQuota := mul(8000, totalQuota) } case 4 { totalQuota := mul(16000, totalQuota) } case 5 { totalQuota := mul(32000, totalQuota) } case 6 { totalQuota := mul(64000, totalQuota) } case 7 { totalQuota := mul(128000, totalQuota) } } if (totalQuota <= quotaUsed) { return (cost, quotaUsed); } unchecked { totalQuota -= quotaUsed; if (cost < totalQuota) { return (0, uint96(quotaUsed + cost)); } else { return (cost - totalQuota, totalQuota + quotaUsed); } } } function getClaimCost(uint256 lockingRatioPct) internal pure returns (uint256) { if (lockingRatioPct < 40) { return 0; } else { /// 1000 * 2^(0..12) unchecked { return 1000 ether * (2 ** ((lockingRatioPct - 40) / 5)); } } } 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 } } } }
// 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/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; /// @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; /// @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; /// @notice withdraw platform fee accumulated. /// Note. withdraw from `address(this)`'s account. function withdrawPlatformFee(address token, uint256 amount) external; /// @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; /// @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; /// 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.(note. the NFTs of the msg.sender will be transfered) 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; /// @notice Place a bid on specified `nftId`'s action /// @param token which token should be transfered to the Flooring for bidding. `0x0` means ETH(native) /// @param amountToTransfer how many `token` should to transfered function placeBidOnAuction( address collection, uint256 nftId, uint256 bidAmount, uint256 bidOptionIdx, address token, uint256 amountToTransfer ) 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; /// @notice Buy `nftId`'s raffle tickets /// @param token which token should be transfered to the Flooring for buying. `0x0` means ETH(native) /// @param amountToTransfer how many `token` should to transfered function buyRaffleTickets( address collectionId, uint256 nftId, uint256 ticketCnt, address token, uint256 amountToTransfer ) 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; /// @notice Owner or Receiver cancel the private offers of `nftIds` function cancelPrivateOffers(address collectionId, uint256[] memory nftIds) external; /// @notice Receiver accept the private offers of `nftIds` function buyerAcceptPrivateOffers(address collectionId, uint256[] memory nftIds) external; /// @notice Receiver accept the private offers of `nftIds` /// @param token which token should be transfered to the Flooring for buying. `0x0` means ETH(native) /// @param amountToTransfer how many `token` should to transfered function buyerAcceptPrivateOffers( address collectionId, uint256[] memory nftIds, address token, uint256 amountToTransfer ) 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) 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 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/contracts/utils/math/Math.sol"; import "openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; import "../library/RollingBuckets.sol"; import "../library/ERC721Transfer.sol"; import "../Errors.sol"; import "../Constants.sol"; import "./User.sol"; import "./Helper.sol"; import {SafeBox, CollectionState, AuctionInfo, CollectionAccount, UserFloorAccount, LockParam} from "./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, address collection, uint256[] memory nftIds, address onBehalfOf ) public { uint256 nftLen = nftIds.length; unchecked { for (uint256 i; i < nftLen; ++i) { collectionState.freeTokenIds.push(nftIds[i]); } } collectionState.floorToken.mint(onBehalfOf, Constants.FLOOR_TOKEN_AMOUNT * nftLen); ERC721Transfer.safeBatchTransferFrom(collection, msg.sender, address(this), nftIds); emit FragmentNft(msg.sender, onBehalfOf, collection, nftIds); } struct LockInfo { bool isInfinite; uint256 currentBucket; uint256 newExpiryBucket; 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(); uint8 vipLevel = uint8(param.vipLevel); uint256 totalCredit = account.ensureVipCredit(vipLevel, param.creditToken); Helper.ensureMaxLocking(collection, vipLevel, param.expiryTs, param.nftIds.length, false); { uint8 maxVipLevel = Constants.getVipLevel(totalCredit); uint256 newLocked = param.nftIds.length; Helper.ensureProxyVipLevel(maxVipLevel, param.collection != param.proxyCollection); Helper.checkAndUpdateSafeboxPeriodQuota(account, maxVipLevel, newLocked.toUint16()); Helper.checkSafeboxUserQuota(account, vipLevel, 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, 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(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), 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; ++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), 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])) 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); } } } function removeExpiredKeysAndRestoreCredits( CollectionState storage collectionState, UserFloorAccount storage userAccount, address collectionId, uint256[] memory nftIds, address onBehalfOf ) public returns (uint256 releasedCredit) { CollectionAccount storage collectionAccount = userAccount.getByKey(collectionId); uint256 removedCnt; uint256[] memory removedIds = new uint256[](nftIds.length); uint256[] memory removedKeys = new uint256[](nftIds.length); for (uint256 i = 0; i < nftIds.length;) { uint256 nftId = nftIds[i]; SafeBoxKey memory safeBoxKey = collectionAccount.getByKey(nftId); SafeBox memory safeBox = collectionState.safeBoxes[nftId]; if (safeBoxKey.keyId == 0) { revert Errors.InvalidParam(); } if (safeBox._isSafeBoxExpired() || !safeBox._isKeyMatchingSafeBox(safeBoxKey)) { removedIds[removedCnt] = nftId; removedKeys[removedCnt] = SafeBoxLib.encodeSafeBoxKey(safeBoxKey); unchecked { ++removedCnt; releasedCredit += safeBoxKey.lockingCredit; } userAccount.updateVipKeyCount(safeBoxKey.vipLevel, -1); collectionAccount.removeSafeboxKey(nftId); } unchecked { ++i; } } if (releasedCredit > 0) { collectionAccount.totalLockingCredit -= releasedCredit.toUint96(); } emit RemoveExpiredKey(msg.sender, onBehalfOf, collectionId, removedIds, removedKeys); } 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, address creditToken, 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(); } uint256[] memory selectedTokenIds = new uint256[](claimCnt); UserFloorAccount storage userAccount = userAccounts[msg.sender]; while (claimCnt > 0) { totalCreditCost += Constants.getClaimCost(Helper.calculateLockingRatioRaw(freeAmount, totalManaged)); /// just compute a deterministic random number uint256 chosenNftIdx = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, totalManaged))) % collection.freeTokenIds.length; unchecked { --claimCnt; --totalManaged; --freeAmount; } selectedTokenIds[claimCnt] = collection.freeTokenIds[chosenNftIdx]; collection.freeTokenIds[chosenNftIdx] = collection.freeTokenIds[collection.freeTokenIds.length - 1]; collection.freeTokenIds.pop(); } { /// calculate cost with waiver quota uint8 vipLevel = Constants.getVipLevel(userAccount.tokenBalance(creditToken)); uint96 waiverUsed = Helper.updateUserCreditWaiver(userAccount); (totalCreditCost, userAccount.creditWaiverUsed) = Constants.getVipClaimCostWithDiscount(totalCreditCost, vipLevel, waiverUsed); } if (totalCreditCost > maxCreditCost) { revert Errors.InsufficientCredit(); } userAccount.transferToken(userAccounts[address(this)], creditToken, totalCreditCost, true); 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 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} 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 checkAndUpdateSafeboxPeriodQuota(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; } function checkSafeboxUserQuota(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]) { 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); } 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 getTokenFeeRateBips(address creditToken, address floorToken, address settleToken) internal pure returns (uint256) { uint256 feeRateBips = Constants.COMMON_FEE_RATE_BIPS; if (settleToken == creditToken) { feeRateBips = Constants.CREDIT_FEE_RATE_BIPS; } else if (settleToken == floorToken) { feeRateBips = Constants.SPEC_FEE_RATE_BIPS; } return feeRateBips; } 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 updateUserCreditWaiver(UserFloorAccount storage account) internal returns (uint96) { if (block.timestamp - account.lastWaiverPeriodTs <= Constants.USER_REDEMPTION_WAIVER_REFRESH_DURATION) { return account.creditWaiverUsed; } else { unchecked { account.lastWaiverPeriodTs = uint32( block.timestamp / Constants.USER_REDEMPTION_WAIVER_REFRESH_DURATION * Constants.USER_REDEMPTION_WAIVER_REFRESH_DURATION ); } account.creditWaiverUsed = 0; return 0; } } 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; } } }
// 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; } 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; /// Whether the auction is triggered by the NFT owner itself? bool isSelfTriggered; uint64 activityId; uint32 feeRateBips; } 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; /// price per ticket uint96 ticketPrice; /// total funds collected by selling tickets uint96 collectedFund; uint64 activityId; address owner; /// total sold tickets amount uint48 ticketSold; uint32 feeRateBips; /// whether the raffle is being settling bool isSettling; /// tickets sold records TicketRecord[] tickets; } 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; uint32 lastQuotaPeriodTs; uint16 safeboxQuotaUsed; 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; } /// Internal Structure struct LockParam { address proxyCollection; address collection; uint256[] nftIds; uint256 expiryTs; uint256 vipLevel; uint256 maxCreditCost; address creditToken; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; import "../Constants.sol"; import "../Errors.sol"; import {UserFloorAccount, CollectionAccount, SafeBoxKey} from "./Structs.sol"; library UserLib { using SafeCast for uint256; /// @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.keys[nftId] = key; } function removeSafeboxKey(CollectionAccount storage account, uint256 nftId) internal { 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 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 { 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"},{"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":"address","name":"settleToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"minimumBid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeRateBips","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionEndTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"safeBoxExpiryTs","type":"uint256"},{"indexed":false,"internalType":"bool","name":"selfTriggered","type":"bool"},{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"}],"name":"AuctionStarted","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
61306a610035600b8282823980515f1a60731461002957634e487b7160e01b5f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040526004361061006f575f3560e01c80635b20b5c6116100585780635b20b5c6146100b3578063ba56df4e146100d2578063d23289c4146100f1575f80fd5b806337d0f32a146100735780633873e16c14610094575b5f80fd5b81801561007e575f80fd5b5061009261008d366004612b43565b610110565b005b81801561009f575f80fd5b506100926100ae366004612bde565b61078f565b8180156100be575f80fd5b506100926100cd366004612c63565b6109cb565b8180156100dd575f80fd5b506100926100ec366004612cbf565b610d78565b8180156100fc575f80fd5b5061009261010b366004612d1b565b6110be565b825160011461013257604051633494a40d60e21b815260040160405180910390fd5b690a968163f0a57b400000816001600160601b0316101561016657604051633494a40d60e21b815260040160405180910390fd5b5f610171885f61126a565b90505f61017d826112b6565b60088a0154909150819061019e90600160401b900463ffffffff1642612dbe565b116101bc5760405163d6516b8960e01b815260040160405180910390fd5b505060408051610120810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101919091526102136201518042612dd1565b6001600160601b0390811682526001600160a01b03841660208084019190915283821660408085018290523360608087018290525f60c088018190526101008801819052608088019490945260a087019190915281519081019091526001600160401b038152845191939092830191610291916202a3009116612dd1565b63ffffffff168152602001306001600160a01b031681525090505f85516001600160401b038111156102c5576102c5612a98565b6040519080825280602002602001820160405280156102ee578160200160208202803683370190505b5090505f86516001600160401b0381111561030b5761030b612a98565b604051908082528060200260200182016040528015610334578160200160208202803683370190505b5087519091505b80156106615760028c015488515f199092019189908390811061036057610360612de4565b60200260200101511061038657604051633494a40d60e21b815260040160405180910390fd5b5f8c60020189838151811061039d5761039d612de4565b6020026020010151815481106103b5576103b5612de4565b905f5260205f2001549050808483815181106103d3576103d3612de4565b60209081029190910101526103e98d828761130e565b6103f28d6113fa565b8660e001906001600160401b031690816001600160401b031681525050858d6004015f8381526020019081526020015f205f820151815f015f6101000a8154816001600160601b0302191690836001600160601b031602179055506020820151815f01600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816001015f6101000a8154816001600160601b0302191690836001600160601b03160217905550606082015181600101600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506080820151816002015f6101000a8154816001600160601b0302191690836001600160601b0316021790555060a082015181600201600c6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060c0820151816003015f6101000a81548160ff02191690831515021790555060e08201518160030160016101000a8154816001600160401b0302191690836001600160401b031602179055506101008201518160030160096101000a81548163ffffffff021916908363ffffffff1602179055509050508560e001518383815181106105b9576105b9612de4565b6001600160401b039092166020928302919091019091015260028d0180546105e390600190612dbe565b815481106105f3576105f3612de4565b905f5260205f2001548d6002018a848151811061061257610612612de4565b60200260200101518154811061062a5761062a612de4565b5f9182526020909120015560028d0180548061064857610648612df8565b600190038181905f5260205f20015f905590555061033b565b50305f90815260208b815260409091209085015183516106b5929190610690906001600160601b038a16612e0c565b335f90815260208f9052604090209291906001600160a01b038b8116908f161461143d565b6106fb8b6203f4806203f47f4201046001600160601b03166106eb866020015163ffffffff166203f4806203f47f919091010490565b6001600160601b03168551611454565b60088b0180546bffffffff00000000000000001916600160401b4263ffffffff16021790556020848101516101008601518651928601516040516001600160a01b038d169433947f9cc535b1ab0d356f0f31161be006f42fcbf8b44752c635e7f734fbe320b4b4e59461077a9489948b948f939290915f908190612e93565b60405180910390a35050505050505050505050565b690a968163f0a57b4000008110156107ba57604051633494a40d60e21b815260040160405180910390fd5b60408051610120810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915261080f6201518042612dd1565b6001600160601b031681526001600160a01b03831660208201526108328261153a565b6001600160601b031660408201523360608201525f60c082018190528854610866919088906001600160a01b031686611575565b63ffffffff1661010082015261087b8261153a565b6001600160601b031660808201523360a08201525f8061089c8a878561159b565b63ffffffff16915091505f86515f6108b49190612e0c565b9050886001600160a01b0316866001600160a01b03160361091957305f90815260208b90526040902087516109149190889084906108f2908a612e0c565b6108fc9190612dd1565b335f90815260208f905260409020929190600161143d565b610978565b305f90815260208b90526040902087516109519190889061093a9089612e0c565b335f90815260208f9052604081209392919061143d565b801561097857305f90815260208b905260408082203383529120610978918b84600161143d565b61010084015184516040516001600160a01b038b169233927f9cc535b1ab0d356f0f31161be006f42fcbf8b44752c635e7f734fbe320b4b4e59261077a9289928e928e928e9290918c905f908d90612f0d565b5f805f6109d78461186d565b604080820151335f90815260208d9052919091209192506109f991908a611954565b50610a428a6040518060a00160405280898152602001610a188961153a565b6001600160601b0316815233602080830191909152855160408301528501516060909101526119a4565b816fffffffffffffffffffffffffffffffff1691508093508194505050505f896004015f8781526020019081526020015f20604051806101200160405290815f82015f9054906101000a90046001600160601b03166001600160601b03166001600160601b031681526020015f8201600c9054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152602001600182015f9054906101000a90046001600160601b03166001600160601b03166001600160601b0316815260200160018201600c9054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152602001600282015f9054906101000a90046001600160601b03166001600160601b03166001600160601b0316815260200160028201600c9054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152602001600382015f9054906101000a900460ff161515151581526020016003820160019054906101000a90046001600160401b03166001600160401b03166001600160401b031681526020016003820160099054906101000a900463ffffffff1663ffffffff1663ffffffff168152505090505f81602001519050610c7d8a5f306001600160a01b03166001600160a01b031681526020019081526020015f2082888c6001600160a01b0316856001600160a01b0316148e5f336001600160a01b03166001600160a01b031681526020019081526020015f2061143d90949392919063ffffffff16565b8315610cb1576001600160a01b0383165f90815260208b90526040808220308352908220610cb1929091908490889061143d565b5f87815260038c01602090815260409182902082516060808201855291546001600160401b03808216835263ffffffff600160401b8304168386018190526001600160a01b03600160601b90930483168488015260e0890151895188519190931681529586018e90529585018c90526001600160601b03169284019290925260808301939093528a169033907f803010fdbef76f36b5d3e5ac091de314dede9c52855c46fd5e8c0b040961a40c9060a00160405180910390a3505050505050505050505050565b5f5b81518110156110b7575f828281518110610d9657610d96612de4565b602002602001015190505f610dab8783611c8e565b9050610db681611ccb565b15610dd457604051631d80cfbf60e21b815260040160405180910390fd5b5f828152600488016020908152604080832081516101208101835281546001600160601b03808216808452600160601b928390046001600160a01b03908116978501979097526001850154808316968501969096529482900486166060840152600284015490811660808401520490931660a08401526003015460ff8116151560c08401526101008082046001600160401b031660e0850152690100000000000000000090910463ffffffff1690830152909103610ea557604051631cacacff60e11b815260040160405180910390fd5b42815f01516001600160601b03161115610ed257604051630a0619fd60e01b815260040160405180910390fd5b60a08101516001600160a01b0316610efd57604051630a0619fd60e01b815260040160405180910390fd5b5f610f2082608001516001600160601b031683610100015163ffffffff16611cfd565b508354600160601b90046001600160a01b03165f90815260208a8152604080832091860151308452908320939450610f5a9392859061143d565b60a082015160408051606081019091525f8082529060208101610f7c8d611d21565b6001600160401b0390811682525f602092830181905283830151895492166bffffffff000000000000000090921691909117600160601b6001600160a01b03878116918202929092178a5582528d83526040808320918e1683526003820190935291902091925090610fef818985611d60565b5f88815260048e0160209081526040808320838155600181018490556002810193909355600390920180546cffffffffffffffffffffffffff1916905560e0880151858201516080808b015185516001600160401b0394851681529485018e905292909116938301939093526001600160601b031660608201526001600160a01b038d811692908716917f4267620590840eb55001e83ca966068950e54f161253b9bcb8ad7903d67e2685910160405180910390a38860010198505050505050505050610d7a565b5050505050565b335f90815260208890526040812085519091906110e49068056bc75e2d63100000612e0c565b305f90815260208b9052604090209091506111049083908a84600161143d565b60408051610120810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101919091526111596201518042612dd1565b6001600160601b031681526001600160a01b038516602082015261117c8461153a565b6001600160601b03166040820152336060820152600160c082018190528b546111b191908b906001600160a01b031688611575565b63ffffffff9081166101008301525f6080830181905260a083018190529081906111ed908e906111e59088908e90611d4316565b8b8b87611e44565b63ffffffff1691509150896001600160a01b0316336001600160a01b03167f9cc535b1ab0d356f0f31161be006f42fcbf8b44752c635e7f734fbe320b4b4e5848c8b8b8961010001518a5f01518960018e60405161125399989796959493929190612f0d565b60405180910390a350505050505050505050505050565b600282015460078301545f91908290829061129590600160801b90046001600160401b031686612dd1565b61129f9190612dd1565b90506112ab82826122d5565b925050505b92915050565b5f60508210156112c95750610e10919050565b60558210156112db5750612a30919050565b605a8210156112ed5750615460919050565b605f8210156112ff575061a8c0919050565b5062015180919050565b919050565b5f8281526003840160205260409020546001600160401b0316156113455760405163985816ed60e01b815260040160405180910390fd5b5f8281526003840160209081526040918290208351815492850151938501516001600160a01b0316600160601b026001600160601b0363ffffffff909516600160401b026bffffffffffffffffffffffff199094166001600160401b0392831617939093179390931691909117905560078401805490916010916113d191600160801b90910416612fa1565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b6008810180546001600160401b0316905f61141483612fa1565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550919050565b61144985848484612301565b6110b78484846123e4565b828214611534575f611467858585612417565b80519091505f8313156114b257825f5b828110156114ab578184828151811061149257611492612de4565b6020908102919091010180519091019052600101611477565b50506114f0565b5f838103905b828110156114ed57818482815181106114d3576114d3612de4565b6020908102919091010180519190910390526001016114b8565b50505b506114ff60018601858361256b565b60078501546001600160401b03168311156110b7575060078401805467ffffffffffffffff19166001600160401b0384161790555b50505050565b5f6001600160601b03821115611571576040516306dfcc6560e41b8152606060048201526024810183905260440160405180910390fd5b5090565b5f841561158e576115878484846126b1565b9050611593565b506107d05b949350505050565b60605f6202a300835f01516001600160601b03166115b99190612dd1565b905083516001600160401b038111156115d4576115d4612a98565b6040519080825280602002602001820160405280156115fd578160200160208202803683370190505b5091505f5b845181101561182b575f85828151811061161e5761161e612de4565b6020026020010151905061163b81886126f590919063ffffffff16565b1561165957604051636bb8676960e11b815260040160405180910390fd5b5f6116648883611c8e565b905061166f81611ccb565b61168c5760405163f1a3275d60e01b815260040160405180910390fd5b61169581612760565b156116b3576040516353b91a5160e01b815260040160405180910390fd5b6116bc886113fa565b8584815181106116ce576116ce612de4565b60200260200101906001600160401b031690816001600160401b03168152505084838151811061170057611700612de4565b6020908102919091018101516001600160401b0390811660e089019081525f94855260048b0183526040948590208951938a01516001600160601b03948516600160601b6001600160a01b039283168102919091178355968b015160608c015190861690821688021760018084019190915560808c015160a08d015196169590911690960293909317600284015560c0890151600390930180549151610100808c015168ffffffffffffffffff1990941695151568ffffffffffffffff00191695909517908416909402939093176cffffffff0000000000000000001916690100000000000000000063ffffffff928316021790925582546bffffffffffffffffffffffff1916600160401b9287169290920267ffffffffffffffff19169190911717905501611602565b50611865856203f4806203f47f4201046001600160601b03166203f4806203f47f63ffffffff861601046001600160601b03168751611454565b935093915050565b61188e60405180606001604052805f81526020015f81526020015f81525090565b6004821061189a575f80fd5b5f6040518060800160405280604051806060016040528061012c8152602001600181526020015f815250815260200160405180606001604052806170808152602001600a815260200160038152508152602001604051806060016040528061e100815260200160148152602001600581525081526020016040518060600160405280620151808152602001602881526020016007815250815250905080836004811061194857611948612de4565b60200201519392505050565b6001600160a01b0381165f9081526004840160205260408120548061197b8560ff16612788565b111561199a576040516369f131af60e11b815260040160405180910390fd5b90505b9392505050565b80515f9081526004830160205260408120825182919082906119c7908790611c8e565b825460028401546001600160601b038082169750600160601b9091046001600160a01b03169550919250165f819003611a1357604051631cacacff60e11b815260040160405180910390fd5b428111611a335760405163ce2abcbb60e01b815260040160405180910390fd5b85602001516001600160601b0316856fffffffffffffffffffffffffffffffff16101580611a755750602086015160018401546001600160601b039182169116115b15611a935760405163724f8deb60e01b815260040160405180910390fd5b85604001516001600160a01b0316846001600160a01b031603611ac957604051631247b42760e11b815260040160405180910390fd5b600383015460ff168015611af55750815460408701516001600160a01b03908116600160601b90920416145b15611b1357604051631247b42760e11b815260040160405180910390fd5b5f856fffffffffffffffffffffffffffffffff16118015611b5f5750611b5d86602001516001600160601b0316866fffffffffffffffffffffffffffffffff16886080015161282c565b155b15611b7d57604051636f5353b160e11b815260040160405180910390fd5b815467ffffffffffffffff19166001600160401b0317825560608601515f90611ba69042612dd1565b905081811115611c53575f611bbe6202a30083612dd1565b8454909150611c0e908a90611bea90600160401b900463ffffffff166203f4806203f47f919091010490565b6001600160601b03166203f4806203f47f8501046001600160601b03166001611454565b835463ffffffff909116600160401b026bffffffff00000000000000001990911617835583546001600160601b0382166bffffffffffffffffffffffff199091161784555b50505060208401516040909401516001600160a01b0316600160601b026001600160601b039094169390931760029093019290925592909150565b5f8181526003830160205260408120805490916001600160401b0390911690036112b057604051635672499160e01b815260040160405180910390fd5b80545f90600160401b900463ffffffff16158015906112b05750815442600160401b90910463ffffffff161092915050565b5f80612710611d0c8486612e0c565b611d169190612fda565b938490039492505050565b600781018054600160401b90046001600160401b031690600861141483612fa1565b6001600160a01b03165f9081526003919091016020526040902090565b5f82815260208490526040902054600160601b90046001600160401b031615611d9c57604051637f471e3f60e11b815260040160405180910390fd5b5f91825260209283526040918290208151815494830151939092015160ff1674010000000000000000000000000000000000000000027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6001600160401b03909416600160601b027fffffffffffffffffffffffff00000000000000000000000000000000000000009095166001600160601b03909316929092179390931791909116179055565b60605f6202a300835f01516001600160601b0316611e629190612dd1565b90505f6203f4806203f47f4201046203f4806203f47f63ffffffff85160104611e8b9190612fed565b6001600160601b0316905060608515611ef157611eee6203f4806203f47f4201046001600160601b0316611ee36203f4806203f47f8a010460078d01546001600160601b0391909116906001600160401b031661289d565b60018c0191906128b2565b90505b86516001600160401b03811115611f0a57611f0a612a98565b604051908082528060200260200182016040528015611f33578160200160208202803683370190505b5093505f5b87518110156122a057611f6d888281518110611f5657611f56612de4565b60200260200101518b6126f590919063ffffffff16565b15611f8b57604051636bb8676960e11b815260040160405180910390fd5b5f611fba8a8a8481518110611fa257611fa2612de4565b60200260200101518d6129db9092919063ffffffff16565b508054909150600160401b900463ffffffff166120315760078b01805460189061200890780100000000000000000000000000000000000000000000000090046001600160401b0316613014565b91906101000a8154816001600160401b0302191690836001600160401b031602179055506120f9565b805463ffffffff600160401b909104811690861681101561206557604051633494a40d60e21b815260040160405180910390fd5b5f6203f4806203f47f4201046203f4806203f47f8401046120869190612fed565b6001600160601b031690508086118061209f5750845181115b156120bd57604051633494a40d60e21b815260040160405180910390fd5b855b818110156120f5578581815181106120d9576120d9612de4565b6020026020010180516120eb90613035565b90526001016120bf565b5050505b80546bffffffff00000000000000001916600160401b63ffffffff8716021781556121238b6113fa565b86838151811061213557612135612de4565b60200260200101906001600160401b031690816001600160401b03168152505085828151811061216757612167612de4565b60200260200101518760e001906001600160401b031690816001600160401b031681525050868b6004015f8b85815181106121a4576121a4612de4565b60209081029190910181015182528181019290925260409081015f208351928401516001600160a01b03908116600160601b9081026001600160601b039586161783559285015160608601518216840290851617600180840191909155608086015160a0870151909216909302931692909217600283015560c08301516003909201805460e08501516101009586015163ffffffff166901000000000000000000026cffffffff000000000000000000196001600160401b0390921690960268ffffffffffffffff00199515159590951668ffffffffffffffffff19909216919091179390931792909216929092179055919091019050611f38565b508051156122c9576122c960018a016001600160601b036203f480426203f47f0104168361256b565b50509550959350505050565b5f815f036122e457505f6112b0565b8183606402816122f6576122f6612fc6565b0460640390506112b0565b6001600160a01b0383165f9081526004850160205260409020548281101561233c57604051638ac4bc7360e01b815260040160405180910390fd5b81156123bf5782810361235e612356876001015460f01c90565b60ff16612788565b811080612374575085546001600160601b031681105b806123825750856002015481105b156123a057604051638ac4bc7360e01b815260040160405180910390fd5b6001600160a01b0385165f9081526004870160205260409020556110b7565b6001600160a01b0384165f908152600486016020526040902083820390555050505050565b6001600160a01b0382165f9081526004840160205260408120805483929061240d908490612dd1565b9091555050505050565b60078301546060906001600160401b03165f818410156124395783915061243e565b508083035b8185101561251457805f036124625761245b6001870186846128b2565b9250612562565b84820361246f8282612dd1565b6001600160401b0381111561248657612486612a98565b6040519080825280602002602001820160405280156124af578160200160208202803683370190505b5093505f6124c16001890188866128b2565b90505f5b8281101561250c578181815181106124df576124df612de4565b60200260200101518682815181106124f9576124f9612de4565b60209081029190910101526001016124c5565b505050612562565b61251e8585612dbe565b6001600160401b0381111561253557612535612a98565b60405190808252806020026020018201604052801561255e578160200160208202803683370190505b5092505b50509392505050565b805160f081111561258f5760405163d571362560e01b815260040160405180910390fd5b61259a815f19612dbe565b8311156125ba5760405163d571362560e01b815260040160405180910390fd5b5f806125cf60f0865b06600a80820492910690565b5f8281526020898152604090912054600190960181029592945090925062ffffff601884021b19905b85811015612697578681015162ffffff811115612628576040516369600fad60e11b815260040160405180910390fd5b80601886021b808486161794505060018501945062ffffff601886021b199250600a850361268e575f86815260208b815260409091208590556126719060f09084048b016125c3565b5f82815260208d905260409020549197509550935062ffffff1992505b506020016125f8565b50505f928352602096909652506040902093909355505050565b5f6101f46001600160a01b03808616908416036126d05750609661199a565b836001600160a01b0316836001600160a01b03160361199a575061012c949350505050565b5f818152600483016020526040812054426001600160601b0390911610158061273757505f8281526005840160205260409020544265ffffffffffff90911610155b8061199d57505f828152600684016020526040902054426001600160601b03909116101561199d565b80545f904290612781906201518090600160401b900463ffffffff16612dd1565b1092915050565b5f60088210612795575f80fd5b81600181146127d357600281146127dd57600381146127e857600481146127f357600581146127fe576006811461280957600781146128145761281c565b617530915061281c565b620186a0915061281c565b620493e0915061281c565b620f4240915061281c565b622dc6c0915061281c565b62989680915061281c565b6301c9c38091505b50670de0b6b3a764000002919050565b5f80606461283a8486612e0c565b6128449190612fda565b90506001811015612853575060015b61285d8185612dd1565b85101561286d575f91505061199d565b5f612879606487612fda565b90506001811015612888575060015b612892818761304a565b159695505050505050565b5f8183106128ab578161199d565b5090919050565b6060818311156128d55760405163d571362560e01b815260040160405180910390fd5b82820360f08111156128fa5760405163d571362560e01b815260040160405180910390fd5b5f816001600160401b0381111561291357612913612a98565b60405190808252806020026020018201604052801561293c578160200160208202803683370190505b5090505f808061294d60f0896125c3565b5f82815260208c90526040902054919350915062ffffff601883021b895b898110156129cb57602086019550818316601885021c868801528160181b9150600184019350600181019050600a84036129c6576129aa60f0826125c3565b5f82815260208f905260409020549196509450925062ffffff91505b61296b565b50949a9950505050505050505050565b5f818152600384016020526040812080549091906001600160401b03168103612a1757604051635672499160e01b815260040160405180910390fd5b612a2082611ccb565b15612a3e57604051631d80cfbf60e21b815260040160405180910390fd5b505f828152602084905260409020805482546001600160401b03908116600160601b909204161461186557604051633c3ca9d760e11b815260040160405180910390fd5b80356001600160a01b0381168114611309575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612abb575f80fd5b813560206001600160401b0380831115612ad757612ad7612a98565b8260051b604051601f19603f83011681018181108482111715612afc57612afc612a98565b604052938452858101830193838101925087851115612b19575f80fd5b83870191505b84821015612b3857813583529183019190830190612b1f565b979650505050505050565b5f805f805f805f60e0888a031215612b59575f80fd5b8735965060208801359550612b7060408901612a82565b9450612b7e60608901612a82565b935060808801356001600160401b03811115612b98575f80fd5b612ba48a828b01612aac565b935050612bb360a08901612a82565b915060c08801356001600160601b0381168114612bce575f80fd5b8091505092959891949750929550565b5f805f805f805f60e0888a031215612bf4575f80fd5b8735965060208801359550612c0b60408901612a82565b9450612c1960608901612a82565b935060808801356001600160401b03811115612c33575f80fd5b612c3f8a828b01612aac565b935050612c4e60a08901612a82565b915060c0880135905092959891949750929550565b5f805f805f805f60e0888a031215612c79575f80fd5b8735965060208801359550612c9060408901612a82565b9450612c9e60608901612a82565b9699959850939660808101359560a0820135955060c0909101359350915050565b5f805f8060808587031215612cd2575f80fd5b8435935060208501359250612ce960408601612a82565b915060608501356001600160401b03811115612d03575f80fd5b612d0f87828801612aac565b91505092959194509250565b5f805f805f805f80610100898b031215612d33575f80fd5b8835975060208901359650612d4a60408a01612a82565b9550612d5860608a01612a82565b945060808901356001600160401b03811115612d72575f80fd5b612d7e8b828c01612aac565b94505060a08901359250612d9460c08a01612a82565b915060e089013590509295985092959890939650565b634e487b7160e01b5f52601160045260245ffd5b818103818111156112b0576112b0612daa565b808201808211156112b0576112b0612daa565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b80820281158282048414176112b0576112b0612daa565b5f8151808452602080850194508084015f5b83811015612e5a5781516001600160401b031687529582019590820190600101612e35565b509495945050505050565b5f8151808452602080850194508084015f5b83811015612e5a57815187529582019590820190600101612e77565b5f610120808352612ea68184018d612e23565b90508281036020840152612eba818c612e65565b6001600160a01b039a909a16604084015250506001600160601b03968716606082015263ffffffff95861660808201529390951660a0840152921660c082015290151560e0820152610100015292915050565b5f610120808352612f208184018d612e23565b90508281036020840152612f34818c612e65565b6001600160a01b039a909a1660408401525050606081019690965263ffffffff9490941660808601526001600160601b039290921660a085015277ffffffffffffffffffffffffffffffffffffffffffffffff1660c0840152151560e08301526101009091015292915050565b5f6001600160401b03808316818103612fbc57612fbc612daa565b6001019392505050565b634e487b7160e01b5f52601260045260245ffd5b5f82612fe857612fe8612fc6565b500490565b6001600160601b0382811682821603908082111561300d5761300d612daa565b5092915050565b5f6001600160401b0382168061302c5761302c612daa565b5f190192915050565b5f8161304357613043612daa565b505f190190565b5f8261305857613058612fc6565b50069056fea164736f6c6343000814000a
Deployed Bytecode
0x7384153730b8279db2a6dc55f2ca39b7fc876a3f04301460806040526004361061006f575f3560e01c80635b20b5c6116100585780635b20b5c6146100b3578063ba56df4e146100d2578063d23289c4146100f1575f80fd5b806337d0f32a146100735780633873e16c14610094575b5f80fd5b81801561007e575f80fd5b5061009261008d366004612b43565b610110565b005b81801561009f575f80fd5b506100926100ae366004612bde565b61078f565b8180156100be575f80fd5b506100926100cd366004612c63565b6109cb565b8180156100dd575f80fd5b506100926100ec366004612cbf565b610d78565b8180156100fc575f80fd5b5061009261010b366004612d1b565b6110be565b825160011461013257604051633494a40d60e21b815260040160405180910390fd5b690a968163f0a57b400000816001600160601b0316101561016657604051633494a40d60e21b815260040160405180910390fd5b5f610171885f61126a565b90505f61017d826112b6565b60088a0154909150819061019e90600160401b900463ffffffff1642612dbe565b116101bc5760405163d6516b8960e01b815260040160405180910390fd5b505060408051610120810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101919091526102136201518042612dd1565b6001600160601b0390811682526001600160a01b03841660208084019190915283821660408085018290523360608087018290525f60c088018190526101008801819052608088019490945260a087019190915281519081019091526001600160401b038152845191939092830191610291916202a3009116612dd1565b63ffffffff168152602001306001600160a01b031681525090505f85516001600160401b038111156102c5576102c5612a98565b6040519080825280602002602001820160405280156102ee578160200160208202803683370190505b5090505f86516001600160401b0381111561030b5761030b612a98565b604051908082528060200260200182016040528015610334578160200160208202803683370190505b5087519091505b80156106615760028c015488515f199092019189908390811061036057610360612de4565b60200260200101511061038657604051633494a40d60e21b815260040160405180910390fd5b5f8c60020189838151811061039d5761039d612de4565b6020026020010151815481106103b5576103b5612de4565b905f5260205f2001549050808483815181106103d3576103d3612de4565b60209081029190910101526103e98d828761130e565b6103f28d6113fa565b8660e001906001600160401b031690816001600160401b031681525050858d6004015f8381526020019081526020015f205f820151815f015f6101000a8154816001600160601b0302191690836001600160601b031602179055506020820151815f01600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506040820151816001015f6101000a8154816001600160601b0302191690836001600160601b03160217905550606082015181600101600c6101000a8154816001600160a01b0302191690836001600160a01b031602179055506080820151816002015f6101000a8154816001600160601b0302191690836001600160601b0316021790555060a082015181600201600c6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060c0820151816003015f6101000a81548160ff02191690831515021790555060e08201518160030160016101000a8154816001600160401b0302191690836001600160401b031602179055506101008201518160030160096101000a81548163ffffffff021916908363ffffffff1602179055509050508560e001518383815181106105b9576105b9612de4565b6001600160401b039092166020928302919091019091015260028d0180546105e390600190612dbe565b815481106105f3576105f3612de4565b905f5260205f2001548d6002018a848151811061061257610612612de4565b60200260200101518154811061062a5761062a612de4565b5f9182526020909120015560028d0180548061064857610648612df8565b600190038181905f5260205f20015f905590555061033b565b50305f90815260208b815260409091209085015183516106b5929190610690906001600160601b038a16612e0c565b335f90815260208f9052604090209291906001600160a01b038b8116908f161461143d565b6106fb8b6203f4806203f47f4201046001600160601b03166106eb866020015163ffffffff166203f4806203f47f919091010490565b6001600160601b03168551611454565b60088b0180546bffffffff00000000000000001916600160401b4263ffffffff16021790556020848101516101008601518651928601516040516001600160a01b038d169433947f9cc535b1ab0d356f0f31161be006f42fcbf8b44752c635e7f734fbe320b4b4e59461077a9489948b948f939290915f908190612e93565b60405180910390a35050505050505050505050565b690a968163f0a57b4000008110156107ba57604051633494a40d60e21b815260040160405180910390fd5b60408051610120810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915261080f6201518042612dd1565b6001600160601b031681526001600160a01b03831660208201526108328261153a565b6001600160601b031660408201523360608201525f60c082018190528854610866919088906001600160a01b031686611575565b63ffffffff1661010082015261087b8261153a565b6001600160601b031660808201523360a08201525f8061089c8a878561159b565b63ffffffff16915091505f86515f6108b49190612e0c565b9050886001600160a01b0316866001600160a01b03160361091957305f90815260208b90526040902087516109149190889084906108f2908a612e0c565b6108fc9190612dd1565b335f90815260208f905260409020929190600161143d565b610978565b305f90815260208b90526040902087516109519190889061093a9089612e0c565b335f90815260208f9052604081209392919061143d565b801561097857305f90815260208b905260408082203383529120610978918b84600161143d565b61010084015184516040516001600160a01b038b169233927f9cc535b1ab0d356f0f31161be006f42fcbf8b44752c635e7f734fbe320b4b4e59261077a9289928e928e928e9290918c905f908d90612f0d565b5f805f6109d78461186d565b604080820151335f90815260208d9052919091209192506109f991908a611954565b50610a428a6040518060a00160405280898152602001610a188961153a565b6001600160601b0316815233602080830191909152855160408301528501516060909101526119a4565b816fffffffffffffffffffffffffffffffff1691508093508194505050505f896004015f8781526020019081526020015f20604051806101200160405290815f82015f9054906101000a90046001600160601b03166001600160601b03166001600160601b031681526020015f8201600c9054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152602001600182015f9054906101000a90046001600160601b03166001600160601b03166001600160601b0316815260200160018201600c9054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152602001600282015f9054906101000a90046001600160601b03166001600160601b03166001600160601b0316815260200160028201600c9054906101000a90046001600160a01b03166001600160a01b03166001600160a01b03168152602001600382015f9054906101000a900460ff161515151581526020016003820160019054906101000a90046001600160401b03166001600160401b03166001600160401b031681526020016003820160099054906101000a900463ffffffff1663ffffffff1663ffffffff168152505090505f81602001519050610c7d8a5f306001600160a01b03166001600160a01b031681526020019081526020015f2082888c6001600160a01b0316856001600160a01b0316148e5f336001600160a01b03166001600160a01b031681526020019081526020015f2061143d90949392919063ffffffff16565b8315610cb1576001600160a01b0383165f90815260208b90526040808220308352908220610cb1929091908490889061143d565b5f87815260038c01602090815260409182902082516060808201855291546001600160401b03808216835263ffffffff600160401b8304168386018190526001600160a01b03600160601b90930483168488015260e0890151895188519190931681529586018e90529585018c90526001600160601b03169284019290925260808301939093528a169033907f803010fdbef76f36b5d3e5ac091de314dede9c52855c46fd5e8c0b040961a40c9060a00160405180910390a3505050505050505050505050565b5f5b81518110156110b7575f828281518110610d9657610d96612de4565b602002602001015190505f610dab8783611c8e565b9050610db681611ccb565b15610dd457604051631d80cfbf60e21b815260040160405180910390fd5b5f828152600488016020908152604080832081516101208101835281546001600160601b03808216808452600160601b928390046001600160a01b03908116978501979097526001850154808316968501969096529482900486166060840152600284015490811660808401520490931660a08401526003015460ff8116151560c08401526101008082046001600160401b031660e0850152690100000000000000000090910463ffffffff1690830152909103610ea557604051631cacacff60e11b815260040160405180910390fd5b42815f01516001600160601b03161115610ed257604051630a0619fd60e01b815260040160405180910390fd5b60a08101516001600160a01b0316610efd57604051630a0619fd60e01b815260040160405180910390fd5b5f610f2082608001516001600160601b031683610100015163ffffffff16611cfd565b508354600160601b90046001600160a01b03165f90815260208a8152604080832091860151308452908320939450610f5a9392859061143d565b60a082015160408051606081019091525f8082529060208101610f7c8d611d21565b6001600160401b0390811682525f602092830181905283830151895492166bffffffff000000000000000090921691909117600160601b6001600160a01b03878116918202929092178a5582528d83526040808320918e1683526003820190935291902091925090610fef818985611d60565b5f88815260048e0160209081526040808320838155600181018490556002810193909355600390920180546cffffffffffffffffffffffffff1916905560e0880151858201516080808b015185516001600160401b0394851681529485018e905292909116938301939093526001600160601b031660608201526001600160a01b038d811692908716917f4267620590840eb55001e83ca966068950e54f161253b9bcb8ad7903d67e2685910160405180910390a38860010198505050505050505050610d7a565b5050505050565b335f90815260208890526040812085519091906110e49068056bc75e2d63100000612e0c565b305f90815260208b9052604090209091506111049083908a84600161143d565b60408051610120810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101919091526111596201518042612dd1565b6001600160601b031681526001600160a01b038516602082015261117c8461153a565b6001600160601b03166040820152336060820152600160c082018190528b546111b191908b906001600160a01b031688611575565b63ffffffff9081166101008301525f6080830181905260a083018190529081906111ed908e906111e59088908e90611d4316565b8b8b87611e44565b63ffffffff1691509150896001600160a01b0316336001600160a01b03167f9cc535b1ab0d356f0f31161be006f42fcbf8b44752c635e7f734fbe320b4b4e5848c8b8b8961010001518a5f01518960018e60405161125399989796959493929190612f0d565b60405180910390a350505050505050505050505050565b600282015460078301545f91908290829061129590600160801b90046001600160401b031686612dd1565b61129f9190612dd1565b90506112ab82826122d5565b925050505b92915050565b5f60508210156112c95750610e10919050565b60558210156112db5750612a30919050565b605a8210156112ed5750615460919050565b605f8210156112ff575061a8c0919050565b5062015180919050565b919050565b5f8281526003840160205260409020546001600160401b0316156113455760405163985816ed60e01b815260040160405180910390fd5b5f8281526003840160209081526040918290208351815492850151938501516001600160a01b0316600160601b026001600160601b0363ffffffff909516600160401b026bffffffffffffffffffffffff199094166001600160401b0392831617939093179390931691909117905560078401805490916010916113d191600160801b90910416612fa1565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b6008810180546001600160401b0316905f61141483612fa1565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550919050565b61144985848484612301565b6110b78484846123e4565b828214611534575f611467858585612417565b80519091505f8313156114b257825f5b828110156114ab578184828151811061149257611492612de4565b6020908102919091010180519091019052600101611477565b50506114f0565b5f838103905b828110156114ed57818482815181106114d3576114d3612de4565b6020908102919091010180519190910390526001016114b8565b50505b506114ff60018601858361256b565b60078501546001600160401b03168311156110b7575060078401805467ffffffffffffffff19166001600160401b0384161790555b50505050565b5f6001600160601b03821115611571576040516306dfcc6560e41b8152606060048201526024810183905260440160405180910390fd5b5090565b5f841561158e576115878484846126b1565b9050611593565b506107d05b949350505050565b60605f6202a300835f01516001600160601b03166115b99190612dd1565b905083516001600160401b038111156115d4576115d4612a98565b6040519080825280602002602001820160405280156115fd578160200160208202803683370190505b5091505f5b845181101561182b575f85828151811061161e5761161e612de4565b6020026020010151905061163b81886126f590919063ffffffff16565b1561165957604051636bb8676960e11b815260040160405180910390fd5b5f6116648883611c8e565b905061166f81611ccb565b61168c5760405163f1a3275d60e01b815260040160405180910390fd5b61169581612760565b156116b3576040516353b91a5160e01b815260040160405180910390fd5b6116bc886113fa565b8584815181106116ce576116ce612de4565b60200260200101906001600160401b031690816001600160401b03168152505084838151811061170057611700612de4565b6020908102919091018101516001600160401b0390811660e089019081525f94855260048b0183526040948590208951938a01516001600160601b03948516600160601b6001600160a01b039283168102919091178355968b015160608c015190861690821688021760018084019190915560808c015160a08d015196169590911690960293909317600284015560c0890151600390930180549151610100808c015168ffffffffffffffffff1990941695151568ffffffffffffffff00191695909517908416909402939093176cffffffff0000000000000000001916690100000000000000000063ffffffff928316021790925582546bffffffffffffffffffffffff1916600160401b9287169290920267ffffffffffffffff19169190911717905501611602565b50611865856203f4806203f47f4201046001600160601b03166203f4806203f47f63ffffffff861601046001600160601b03168751611454565b935093915050565b61188e60405180606001604052805f81526020015f81526020015f81525090565b6004821061189a575f80fd5b5f6040518060800160405280604051806060016040528061012c8152602001600181526020015f815250815260200160405180606001604052806170808152602001600a815260200160038152508152602001604051806060016040528061e100815260200160148152602001600581525081526020016040518060600160405280620151808152602001602881526020016007815250815250905080836004811061194857611948612de4565b60200201519392505050565b6001600160a01b0381165f9081526004840160205260408120548061197b8560ff16612788565b111561199a576040516369f131af60e11b815260040160405180910390fd5b90505b9392505050565b80515f9081526004830160205260408120825182919082906119c7908790611c8e565b825460028401546001600160601b038082169750600160601b9091046001600160a01b03169550919250165f819003611a1357604051631cacacff60e11b815260040160405180910390fd5b428111611a335760405163ce2abcbb60e01b815260040160405180910390fd5b85602001516001600160601b0316856fffffffffffffffffffffffffffffffff16101580611a755750602086015160018401546001600160601b039182169116115b15611a935760405163724f8deb60e01b815260040160405180910390fd5b85604001516001600160a01b0316846001600160a01b031603611ac957604051631247b42760e11b815260040160405180910390fd5b600383015460ff168015611af55750815460408701516001600160a01b03908116600160601b90920416145b15611b1357604051631247b42760e11b815260040160405180910390fd5b5f856fffffffffffffffffffffffffffffffff16118015611b5f5750611b5d86602001516001600160601b0316866fffffffffffffffffffffffffffffffff16886080015161282c565b155b15611b7d57604051636f5353b160e11b815260040160405180910390fd5b815467ffffffffffffffff19166001600160401b0317825560608601515f90611ba69042612dd1565b905081811115611c53575f611bbe6202a30083612dd1565b8454909150611c0e908a90611bea90600160401b900463ffffffff166203f4806203f47f919091010490565b6001600160601b03166203f4806203f47f8501046001600160601b03166001611454565b835463ffffffff909116600160401b026bffffffff00000000000000001990911617835583546001600160601b0382166bffffffffffffffffffffffff199091161784555b50505060208401516040909401516001600160a01b0316600160601b026001600160601b039094169390931760029093019290925592909150565b5f8181526003830160205260408120805490916001600160401b0390911690036112b057604051635672499160e01b815260040160405180910390fd5b80545f90600160401b900463ffffffff16158015906112b05750815442600160401b90910463ffffffff161092915050565b5f80612710611d0c8486612e0c565b611d169190612fda565b938490039492505050565b600781018054600160401b90046001600160401b031690600861141483612fa1565b6001600160a01b03165f9081526003919091016020526040902090565b5f82815260208490526040902054600160601b90046001600160401b031615611d9c57604051637f471e3f60e11b815260040160405180910390fd5b5f91825260209283526040918290208151815494830151939092015160ff1674010000000000000000000000000000000000000000027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6001600160401b03909416600160601b027fffffffffffffffffffffffff00000000000000000000000000000000000000009095166001600160601b03909316929092179390931791909116179055565b60605f6202a300835f01516001600160601b0316611e629190612dd1565b90505f6203f4806203f47f4201046203f4806203f47f63ffffffff85160104611e8b9190612fed565b6001600160601b0316905060608515611ef157611eee6203f4806203f47f4201046001600160601b0316611ee36203f4806203f47f8a010460078d01546001600160601b0391909116906001600160401b031661289d565b60018c0191906128b2565b90505b86516001600160401b03811115611f0a57611f0a612a98565b604051908082528060200260200182016040528015611f33578160200160208202803683370190505b5093505f5b87518110156122a057611f6d888281518110611f5657611f56612de4565b60200260200101518b6126f590919063ffffffff16565b15611f8b57604051636bb8676960e11b815260040160405180910390fd5b5f611fba8a8a8481518110611fa257611fa2612de4565b60200260200101518d6129db9092919063ffffffff16565b508054909150600160401b900463ffffffff166120315760078b01805460189061200890780100000000000000000000000000000000000000000000000090046001600160401b0316613014565b91906101000a8154816001600160401b0302191690836001600160401b031602179055506120f9565b805463ffffffff600160401b909104811690861681101561206557604051633494a40d60e21b815260040160405180910390fd5b5f6203f4806203f47f4201046203f4806203f47f8401046120869190612fed565b6001600160601b031690508086118061209f5750845181115b156120bd57604051633494a40d60e21b815260040160405180910390fd5b855b818110156120f5578581815181106120d9576120d9612de4565b6020026020010180516120eb90613035565b90526001016120bf565b5050505b80546bffffffff00000000000000001916600160401b63ffffffff8716021781556121238b6113fa565b86838151811061213557612135612de4565b60200260200101906001600160401b031690816001600160401b03168152505085828151811061216757612167612de4565b60200260200101518760e001906001600160401b031690816001600160401b031681525050868b6004015f8b85815181106121a4576121a4612de4565b60209081029190910181015182528181019290925260409081015f208351928401516001600160a01b03908116600160601b9081026001600160601b039586161783559285015160608601518216840290851617600180840191909155608086015160a0870151909216909302931692909217600283015560c08301516003909201805460e08501516101009586015163ffffffff166901000000000000000000026cffffffff000000000000000000196001600160401b0390921690960268ffffffffffffffff00199515159590951668ffffffffffffffffff19909216919091179390931792909216929092179055919091019050611f38565b508051156122c9576122c960018a016001600160601b036203f480426203f47f0104168361256b565b50509550959350505050565b5f815f036122e457505f6112b0565b8183606402816122f6576122f6612fc6565b0460640390506112b0565b6001600160a01b0383165f9081526004850160205260409020548281101561233c57604051638ac4bc7360e01b815260040160405180910390fd5b81156123bf5782810361235e612356876001015460f01c90565b60ff16612788565b811080612374575085546001600160601b031681105b806123825750856002015481105b156123a057604051638ac4bc7360e01b815260040160405180910390fd5b6001600160a01b0385165f9081526004870160205260409020556110b7565b6001600160a01b0384165f908152600486016020526040902083820390555050505050565b6001600160a01b0382165f9081526004840160205260408120805483929061240d908490612dd1565b9091555050505050565b60078301546060906001600160401b03165f818410156124395783915061243e565b508083035b8185101561251457805f036124625761245b6001870186846128b2565b9250612562565b84820361246f8282612dd1565b6001600160401b0381111561248657612486612a98565b6040519080825280602002602001820160405280156124af578160200160208202803683370190505b5093505f6124c16001890188866128b2565b90505f5b8281101561250c578181815181106124df576124df612de4565b60200260200101518682815181106124f9576124f9612de4565b60209081029190910101526001016124c5565b505050612562565b61251e8585612dbe565b6001600160401b0381111561253557612535612a98565b60405190808252806020026020018201604052801561255e578160200160208202803683370190505b5092505b50509392505050565b805160f081111561258f5760405163d571362560e01b815260040160405180910390fd5b61259a815f19612dbe565b8311156125ba5760405163d571362560e01b815260040160405180910390fd5b5f806125cf60f0865b06600a80820492910690565b5f8281526020898152604090912054600190960181029592945090925062ffffff601884021b19905b85811015612697578681015162ffffff811115612628576040516369600fad60e11b815260040160405180910390fd5b80601886021b808486161794505060018501945062ffffff601886021b199250600a850361268e575f86815260208b815260409091208590556126719060f09084048b016125c3565b5f82815260208d905260409020549197509550935062ffffff1992505b506020016125f8565b50505f928352602096909652506040902093909355505050565b5f6101f46001600160a01b03808616908416036126d05750609661199a565b836001600160a01b0316836001600160a01b03160361199a575061012c949350505050565b5f818152600483016020526040812054426001600160601b0390911610158061273757505f8281526005840160205260409020544265ffffffffffff90911610155b8061199d57505f828152600684016020526040902054426001600160601b03909116101561199d565b80545f904290612781906201518090600160401b900463ffffffff16612dd1565b1092915050565b5f60088210612795575f80fd5b81600181146127d357600281146127dd57600381146127e857600481146127f357600581146127fe576006811461280957600781146128145761281c565b617530915061281c565b620186a0915061281c565b620493e0915061281c565b620f4240915061281c565b622dc6c0915061281c565b62989680915061281c565b6301c9c38091505b50670de0b6b3a764000002919050565b5f80606461283a8486612e0c565b6128449190612fda565b90506001811015612853575060015b61285d8185612dd1565b85101561286d575f91505061199d565b5f612879606487612fda565b90506001811015612888575060015b612892818761304a565b159695505050505050565b5f8183106128ab578161199d565b5090919050565b6060818311156128d55760405163d571362560e01b815260040160405180910390fd5b82820360f08111156128fa5760405163d571362560e01b815260040160405180910390fd5b5f816001600160401b0381111561291357612913612a98565b60405190808252806020026020018201604052801561293c578160200160208202803683370190505b5090505f808061294d60f0896125c3565b5f82815260208c90526040902054919350915062ffffff601883021b895b898110156129cb57602086019550818316601885021c868801528160181b9150600184019350600181019050600a84036129c6576129aa60f0826125c3565b5f82815260208f905260409020549196509450925062ffffff91505b61296b565b50949a9950505050505050505050565b5f818152600384016020526040812080549091906001600160401b03168103612a1757604051635672499160e01b815260040160405180910390fd5b612a2082611ccb565b15612a3e57604051631d80cfbf60e21b815260040160405180910390fd5b505f828152602084905260409020805482546001600160401b03908116600160601b909204161461186557604051633c3ca9d760e11b815260040160405180910390fd5b80356001600160a01b0381168114611309575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612abb575f80fd5b813560206001600160401b0380831115612ad757612ad7612a98565b8260051b604051601f19603f83011681018181108482111715612afc57612afc612a98565b604052938452858101830193838101925087851115612b19575f80fd5b83870191505b84821015612b3857813583529183019190830190612b1f565b979650505050505050565b5f805f805f805f60e0888a031215612b59575f80fd5b8735965060208801359550612b7060408901612a82565b9450612b7e60608901612a82565b935060808801356001600160401b03811115612b98575f80fd5b612ba48a828b01612aac565b935050612bb360a08901612a82565b915060c08801356001600160601b0381168114612bce575f80fd5b8091505092959891949750929550565b5f805f805f805f60e0888a031215612bf4575f80fd5b8735965060208801359550612c0b60408901612a82565b9450612c1960608901612a82565b935060808801356001600160401b03811115612c33575f80fd5b612c3f8a828b01612aac565b935050612c4e60a08901612a82565b915060c0880135905092959891949750929550565b5f805f805f805f60e0888a031215612c79575f80fd5b8735965060208801359550612c9060408901612a82565b9450612c9e60608901612a82565b9699959850939660808101359560a0820135955060c0909101359350915050565b5f805f8060808587031215612cd2575f80fd5b8435935060208501359250612ce960408601612a82565b915060608501356001600160401b03811115612d03575f80fd5b612d0f87828801612aac565b91505092959194509250565b5f805f805f805f80610100898b031215612d33575f80fd5b8835975060208901359650612d4a60408a01612a82565b9550612d5860608a01612a82565b945060808901356001600160401b03811115612d72575f80fd5b612d7e8b828c01612aac565b94505060a08901359250612d9460c08a01612a82565b915060e089013590509295985092959890939650565b634e487b7160e01b5f52601160045260245ffd5b818103818111156112b0576112b0612daa565b808201808211156112b0576112b0612daa565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b80820281158282048414176112b0576112b0612daa565b5f8151808452602080850194508084015f5b83811015612e5a5781516001600160401b031687529582019590820190600101612e35565b509495945050505050565b5f8151808452602080850194508084015f5b83811015612e5a57815187529582019590820190600101612e77565b5f610120808352612ea68184018d612e23565b90508281036020840152612eba818c612e65565b6001600160a01b039a909a16604084015250506001600160601b03968716606082015263ffffffff95861660808201529390951660a0840152921660c082015290151560e0820152610100015292915050565b5f610120808352612f208184018d612e23565b90508281036020840152612f34818c612e65565b6001600160a01b039a909a1660408401525050606081019690965263ffffffff9490941660808601526001600160601b039290921660a085015277ffffffffffffffffffffffffffffffffffffffffffffffff1660c0840152151560e08301526101009091015292915050565b5f6001600160401b03808316818103612fbc57612fbc612daa565b6001019392505050565b634e487b7160e01b5f52601260045260245ffd5b5f82612fe857612fe8612fc6565b500490565b6001600160601b0382811682821603908082111561300d5761300d612daa565b5092915050565b5f6001600160401b0382168061302c5761302c612daa565b5f190192915050565b5f8161304357613043612daa565b505f190190565b5f8261305857613058612fc6565b50069056fea164736f6c6343000814000a
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.